mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
🧗 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:
parent
d6a65f5a08
commit
97e1cdd224
5 changed files with 299 additions and 94 deletions
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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('');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
178
packages/data-schemas/src/utils/object-traverse.ts
Normal file
178
packages/data-schemas/src/utils/object-traverse.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue