🧗 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

@ -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);
},
};
}