@gensx/react
Version:
React hooks and components for GenSX AI workflows.
143 lines (139 loc) • 5.64 kB
JavaScript
/**
* Check out the docs at https://www.gensx.com/docs
* Find us on Github https://github.com/gensx-inc/gensx
* Find us on Discord https://discord.gg/F5BSU8Kc
*/
import '../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/index.js';
import { useState, useRef, useEffect } from 'react';
import { _deepClone } from '../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/helpers.js';
import { applyPatch } from '../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/core.js';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
function useObject(events, label) {
const [result, setResult] = useState(undefined);
// Store the reconstructed object and last processed event index
const reconstructedRef = useRef({});
const lastIndexRef = useRef(-1);
const lastEventsRef = useRef([]);
useEffect(() => {
// Find all relevant object events
const objectEvents = events.filter((event) => event.type === "object" && event.label === label);
// Detect reset: events array replaced or truncated
const isReset = events !== lastEventsRef.current ||
objectEvents.length < lastIndexRef.current + 1;
if (isReset) {
reconstructedRef.current = {};
lastIndexRef.current = -1;
}
// Apply only new patches
for (let i = lastIndexRef.current + 1; i < objectEvents.length; i++) {
const event = objectEvents[i];
if (event.isInitial) {
reconstructedRef.current = {};
}
try {
reconstructedRef.current = applyObjectPatches(event.patches, reconstructedRef.current);
}
catch (error) {
console.warn(`Failed to apply patches for object "${label}":`, error);
}
}
lastIndexRef.current = objectEvents.length - 1;
lastEventsRef.current = events;
setResult(objectEvents.length > 0 ? reconstructedRef.current : undefined);
}, [events, label]);
return result;
}
/**
* Apply a JSON patch to reconstruct object state. This is useful for consumers who want to reconstruct the full object state from patches.
*
* @param patches - The JSON patch operations to apply.
* @param currentState - The current state of the object (defaults to empty object).
* @returns The new state after applying the patches.
*/
function applyObjectPatches(patches, currentState = {}) {
let document = _deepClone(currentState);
let standardPatches = [];
for (const operation of patches) {
if (operation.op === "string-append") {
// Handle string append operation
if (operation.path === "") {
// Root-level string append
if (typeof document === "string") {
document = document + operation.value;
}
else {
// Warn and skip instead of throwing or replacing
console.warn(`Cannot apply string-append: root value is not a string. Skipping operation.`);
// Do nothing
}
continue;
}
const pathParts = operation.path.split("/").slice(1); // Remove empty first element
const target = getValueByPath(document, pathParts.slice(0, -1));
const property = pathParts[pathParts.length - 1];
if (typeof target === "object" && target !== null) {
const currentValue = target[property];
if (typeof currentValue === "string") {
target[property] =
currentValue + operation.value;
}
else {
// Warn and skip instead of replacing
console.warn(`Cannot apply string-append: target path '${operation.path}' is not a string. Skipping operation.`);
// Do nothing
}
}
else {
// Warn and skip instead of throwing
console.warn(`Cannot apply string-append: target path '${operation.path}' does not exist or is not an object. Skipping operation.`);
// Do nothing
}
}
else {
// Handle standard JSON Patch operations
standardPatches.push(operation);
}
}
if (standardPatches.length > 0) {
const result = applyPatch(document, _deepClone(standardPatches));
return result.newDocument;
}
return document;
}
/**
* Helper function to get a value by path in an object or array (RFC 6901 compliant)
*/
function getValueByPath(obj, path) {
let current = obj;
for (const segment of path) {
if (Array.isArray(current)) {
const idx = Number(segment);
if (!Number.isNaN(idx) && idx >= 0 && idx < current.length) {
current = current[idx];
}
else {
return undefined;
}
}
else if (isPlainObject(current)) {
if (segment in current) {
current = current[segment];
}
else {
return undefined;
}
}
else {
return undefined;
}
}
return current;
}
/**
* Utility to check if a value is a non-null, non-array object
*/
function isPlainObject(val) {
return typeof val === "object" && val !== null && !Array.isArray(val);
}
export { useObject };
//# sourceMappingURL=use-object.js.map