🧗 refactor: Replace traverse package with Minimal Traversal for Logging (#8687)

* 📦 chore: Remove `keyv` from peerDependencies in package.json and package-lock.json for data-schemas

* refactor: replace traverse import with custom object-traverse utility for better control and error handling during logging

* chore(data-schemas): bump version to 0.0.15 and remove unused dependencies

* refactor: optimize message construction in debugTraverse

* chore: update Node.js version to 20.x in data-schemas workflow
This commit is contained in:
Danny Avila 2025-07-27 11:47:37 -04:00 committed by GitHub
parent d6a65f5a08
commit 97e1cdd224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 299 additions and 94 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@librechat/data-schemas",
"version": "0.0.13",
"version": "0.0.15",
"description": "Mongoose schemas and models for LibreChat",
"type": "module",
"main": "dist/index.cjs",
@ -48,7 +48,6 @@
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.0",
"@types/traverse": "^0.6.37",
"jest": "^29.5.0",
"jest-junit": "^16.0.0",
"mongodb-memory-server": "^10.1.4",
@ -62,14 +61,12 @@
},
"peerDependencies": {
"jsonwebtoken": "^9.0.2",
"keyv": "^5.3.2",
"klona": "^2.0.6",
"librechat-data-provider": "*",
"lodash": "^4.17.21",
"meilisearch": "^0.38.0",
"mongoose": "^8.12.1",
"nanoid": "^3.3.7",
"traverse": "^0.6.11",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
},

View file

@ -1,6 +1,7 @@
import { klona } from 'klona';
import winston from 'winston';
import traverse from 'traverse';
import traverse from '../utils/object-traverse';
import type { TraverseContext } from '../utils/object-traverse';
const SPLAT_SYMBOL = Symbol.for('splat');
const MESSAGE_SYMBOL = Symbol.for('message');
@ -123,15 +124,17 @@ const debugTraverse = winston.format.printf(
return `${timestamp} ${level}: ${JSON.stringify(message)}`;
}
let msg = `${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`;
const msgParts: string[] = [
`${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`,
];
try {
if (level !== 'debug') {
return msg;
return msgParts[0];
}
if (!metadata) {
return msg;
return msgParts[0];
}
// Type-safe access to SPLAT_SYMBOL using bracket notation
@ -140,59 +143,66 @@ const debugTraverse = winston.format.printf(
const debugValue = Array.isArray(splatArray) ? splatArray[0] : undefined;
if (!debugValue) {
return msg;
return msgParts[0];
}
if (debugValue && Array.isArray(debugValue)) {
msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
return msg;
msgParts.push(`\n${JSON.stringify(debugValue.map(condenseArray))}`);
return msgParts.join('');
}
if (typeof debugValue !== 'object') {
return (msg += ` ${debugValue}`);
msgParts.push(` ${debugValue}`);
return msgParts.join('');
}
msg += '\n{';
msgParts.push('\n{');
const copy = klona(metadata);
try {
const traversal = traverse(copy);
traversal.forEach(function (this: TraverseContext, value: unknown) {
if (typeof this?.key === 'symbol') {
return;
}
traverse(copy).forEach(function (this: traverse.TraverseContext, value: unknown) {
if (typeof this?.key === 'symbol') {
return;
}
let _parentKey = '';
const parent = this.parent;
let _parentKey = '';
const parent = this.parent;
if (typeof parent?.key !== 'symbol' && parent?.key !== undefined) {
_parentKey = String(parent.key);
}
if (typeof parent?.key !== 'symbol' && parent?.key) {
_parentKey = parent.key;
}
const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
const currentKey = this?.key ?? 'unknown';
const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
const currentKey = this?.key ?? 'unknown';
if (this.isLeaf && typeof value === 'string') {
const truncatedText = truncateLongStrings(value);
msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`);
} else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
const currentMessage = `\n${tabs}// ${value.length} ${String(currentKey).replace(/s$/, '')}(s)`;
this.update(currentMessage);
msgParts.push(currentMessage);
const stringifiedArray = value.map(condenseArray);
msgParts.push(`\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`);
} else if (this.isLeaf && typeof value === 'function') {
msgParts.push(`\n${tabs}${parentKey}${currentKey}: function,`);
} else if (this.isLeaf) {
msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${value},`);
}
});
} catch (e: unknown) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
msgParts.push(`\n[LOGGER TRAVERSAL ERROR] ${errorMessage}`);
}
if (this.isLeaf && typeof value === 'string') {
const truncatedText = truncateLongStrings(value);
msg += `\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`;
} else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
const currentMessage = `\n${tabs}// ${value.length} ${currentKey.replace(/s$/, '')}(s)`;
this.update(currentMessage, true);
msg += currentMessage;
const stringifiedArray = value.map(condenseArray);
msg += `\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`;
} else if (this.isLeaf && typeof value === 'function') {
msg += `\n${tabs}${parentKey}${currentKey}: function,`;
} else if (this.isLeaf) {
msg += `\n${tabs}${parentKey}${currentKey}: ${value},`;
}
});
msg += '\n}';
return msg;
msgParts.push('\n}');
return msgParts.join('');
} catch (e: unknown) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
return (msg += `\n[LOGGER PARSING ERROR] ${errorMessage}`);
msgParts.push(`\n[LOGGER PARSING ERROR] ${errorMessage}`);
return msgParts.join('');
}
},
);

View file

@ -0,0 +1,178 @@
/**
* ESM-native object traversal utility
* Simplified implementation focused on the forEach use case
*/
export interface TraverseContext {
node: unknown;
path: (string | number)[];
parent: TraverseContext | undefined;
key: string | number | undefined;
isLeaf: boolean;
notLeaf: boolean;
isRoot: boolean;
notRoot: boolean;
level: number;
circular: TraverseContext | null;
update: (value: unknown, stopHere?: boolean) => void;
remove: () => void;
}
type ForEachCallback = (this: TraverseContext, value: unknown) => void;
// Type guards for proper typing
type TraversableObject = Record<string | number, unknown> | unknown[];
function isObject(value: unknown): value is TraversableObject {
if (value === null || typeof value !== 'object') {
return false;
}
// Treat these built-in types as leaf nodes, not objects to traverse
if (value instanceof Date) return false;
if (value instanceof RegExp) return false;
if (value instanceof Error) return false;
if (value instanceof URL) return false;
// Check for Buffer (Node.js)
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return false;
// Check for TypedArrays and ArrayBuffer
if (ArrayBuffer.isView(value)) return false;
if (value instanceof ArrayBuffer) return false;
if (value instanceof SharedArrayBuffer) return false;
// Check for other built-in types that shouldn't be traversed
if (value instanceof Promise) return false;
if (value instanceof WeakMap) return false;
if (value instanceof WeakSet) return false;
if (value instanceof Map) return false;
if (value instanceof Set) return false;
// Check if it's a primitive wrapper object
const stringTag = Object.prototype.toString.call(value);
if (
stringTag === '[object Boolean]' ||
stringTag === '[object Number]' ||
stringTag === '[object String]'
) {
return false;
}
return true;
}
// Helper to safely set a property on an object or array
function setProperty(obj: TraversableObject, key: string | number, value: unknown): void {
if (Array.isArray(obj) && typeof key === 'number') {
obj[key] = value;
} else if (!Array.isArray(obj) && typeof key === 'string') {
obj[key] = value;
} else if (!Array.isArray(obj) && typeof key === 'number') {
// Handle numeric keys on objects
obj[key] = value;
}
}
// Helper to safely delete a property from an object
function deleteProperty(obj: TraversableObject, key: string | number): void {
if (Array.isArray(obj) && typeof key === 'number') {
// For arrays, we should use splice, but this is handled in remove()
// This function is only called for non-array deletion
return;
}
if (!Array.isArray(obj)) {
delete obj[key];
}
}
function forEach(obj: unknown, callback: ForEachCallback): void {
const visited = new WeakSet<object>();
function walk(node: unknown, path: (string | number)[] = [], parent?: TraverseContext): void {
// Check for circular references
let circular: TraverseContext | null = null;
if (isObject(node)) {
if (visited.has(node)) {
// Find the circular reference in the parent chain
let p = parent;
while (p) {
if (p.node === node) {
circular = p;
break;
}
p = p.parent;
}
return; // Skip circular references
}
visited.add(node);
}
const key = path.length > 0 ? path[path.length - 1] : undefined;
const isRoot = path.length === 0;
const level = path.length;
// Determine if this is a leaf node
const isLeaf =
!isObject(node) ||
(Array.isArray(node) && node.length === 0) ||
Object.keys(node).length === 0;
// Create context
const context: TraverseContext = {
node,
path: [...path],
parent,
key,
isLeaf,
notLeaf: !isLeaf,
isRoot,
notRoot: !isRoot,
level,
circular,
update(value: unknown) {
if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
setProperty(parent.node, key, value);
}
this.node = value;
},
remove() {
if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
if (Array.isArray(parent.node) && typeof key === 'number') {
parent.node.splice(key, 1);
} else {
deleteProperty(parent.node, key);
}
}
},
};
// Call the callback with the context
callback.call(context, node);
// Traverse children if not circular and is an object
if (!circular && isObject(node) && !isLeaf) {
if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
walk(node[i], [...path, i], context);
}
} else {
for (const [childKey, childValue] of Object.entries(node)) {
walk(childValue, [...path, childKey], context);
}
}
}
}
walk(obj);
}
// Main traverse function that returns an object with forEach method
export default function traverse(obj: unknown) {
return {
forEach(callback: ForEachCallback): void {
forEach(obj, callback);
},
};
}