client
Documentation for using streamListDiff in the browser.
IMPORT
import { streamListDiff } from "@donedeal0/superdiff/client";
FORMAT
Input
In a browser environment,
ReadableStream
refers to the browser's streaming API, andFile
refers to an uploaded or local file. Examples are provided in the #usage section below.
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
useWorker?: boolean; // true by default
showWarnings?: boolean; // true by default
}
prevList
: the original object list.nextList
: the new object list.referenceProperty
: a property common to all objects in your lists (e.g.id
).options
chunksSize
the number of object diffs returned by each streamed chunk. (e.g.0
= 1 object diff per chunk,10
= 10 object diffs per chunk).showOnly
gives you the option to return only the values whose status you are interested in (e.g.["added", "equal"]
).considerMoveAsUpdate
: if set totrue
amoved
value will be considered asupdated
.useWorker
: if set totrue
, the diff will be run in a worker for maximum performance. Only recommended for large lists (e.g. +100,000 items).showWarnings
: if set totrue
, potential warnings will be displayed in the console.
Using Readable streams may impact workers' performance since they need to be converted to arrays. Consider using arrays or files for optimal performance. Alternatively, you can turn the useWorker
option off.
Output
The objects diff are grouped into arrays - called chunks
- and are consumed thanks to an event listener. You have access to 3 events:
data
: to be notified when a new chunk of object diffs is available.finish
: to be notified when the stream is finished.error
: to be notified if an error occurs during the stream.
interface StreamListener<T> {
on(event: "data", listener: (chunk: StreamListDiff<T>[]) => void);
on(event: "finish", listener: () => void);
on(event: "error", listener: (error: Error) => void);
}
type StreamListDiff<T extends Record<string, unknown>> = {
currentValue: T | null;
previousValue: T | null;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: "added" | "deleted" | "moved" | "updated" | "equal";
};
USAGE
Input
Array
const diff = streamListDiff(
[
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }
],
[
{ id: 0, name: "Item 0" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item Three" },
],
"id",
{ chunksSize: 2 }
);
Stream
const previousStream = new ReadableStream({
start(controller) {
previousArray.forEach((value) => controller.enqueue(value));
controller.close();
},
});
const nextStream = new ReadableStream({
start(controller) {
nextArray.forEach((value) => controller.enqueue(value));
controller.close();
},
});
const diff = streamListDiff(previousStream, nextStream, "id", { chunksSize: 2 });
File
const previousFile = new File([JSON.stringify(previousArrayFile)],
"previousArray.json",
{ type: "application/json" });
const nextFile = new File([JSON.stringify(nextArrayFile)],
"nextArray.json",
{ type: "application/json" });
const diff = streamListDiff(previousFile, nextFile, "id", { chunksSize: 10 });
Example
const diff = streamListDiff(
[
- { id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" }
],
[
+ { id: 0, name: "Item 0" },
{ id: 2, name: "Item 2" },
+ { id: 3, name: "Item Three" },
],
"id",
{ chunksSize: 2 }
);
Output
diff.on("data", (chunk) => {
// first chunk received (2 object diffs)
[
+ {
+ previousValue: null,
+ currentValue: { id: 0, name: 'Item 0' },
+ prevIndex: null,
+ newIndex: 0,
+ indexDiff: null,
+ status: 'added'
+ },
- {
- previousValue: { id: 1, name: 'Item 1' },
- currentValue: null,
- prevIndex: 0,
- newIndex: null,
- indexDiff: null,
- status: 'deleted'
- }
]
// second chunk received (2 object diffs)
[
{
previousValue: { id: 2, name: 'Item 2' },
currentValue: { id: 2, name: 'Item 2' },
prevIndex: 1,
newIndex: 1,
indexDiff: 0,
status: 'equal'
},
+ {
+ previousValue: { id: 3, name: 'Item 3' },
+ currentValue: { id: 3, name: 'Item Three' },
+ prevIndex: 2,
+ newIndex: 2,
+ indexDiff: 0,
+ status: 'updated'
+ },
]
});
diff.on("finish", () => console.log("The full diff is available."))
diff.on("error", (err) => console.log(err))
REACT
Here is an example of its use in a React application.
export function useListDiff() {
const [list, setList] = useState<StreamListDiff<Data>[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const [isError, setIsError] = useState<Error>();
const getListDiff = useCallback(async () => {
setIsStreaming(true);
const [prevList, nextList] = await fetchLists()
const diff = streamListDiff(prevList, nextList, "id", {
chunksSize: 50000, // Large chunks size for fewer updates
useWorker: true,
});
diff.on("data", (chunk) => {
// Schedule state updates on the next frame for smooth rendering
requestAnimationFrame(() => {
setList((prev) => [...prev, ...chunk]);
});
});
diff.on("finish", () => setIsStreaming(false));
diff.on("error", (err) => {
setIsStreaming(false);
setIsError(err);
});
}, []);
useEffect(() => {
getListDiff();
}, [getListDiff]);
return {
list,
isStreaming,
isError,
};
}
Last updated