-
-
Notifications
You must be signed in to change notification settings - Fork 0
traverse walk()
Walks an object tree non-recursively, visiting all values with customizable processors.
This is the foundation traversal module used by clone, assemble, deref, and preprocess. It uses a stack-based approach to avoid recursion limits and handles circular references.
import walk from 'deep6/traverse/walk.js';
const result = [];
walk(
{a: 1, b: {c: 2}},
{
processOther: (v, ctx) => result.push(v),
circular: true
}
);
// result contains all primitive values: [2, 1]
// (reverse order because the stack processes depth-first)Arguments:
-
o— a required value to traverse. It can be anything. -
options— an optional object. The following optional properties are recognized:-
processObject— a function(object, context) => voidcalled for each plain object (not matched by the registry or filters). Default: pushes all property values onto the stack. -
processOther— a function(value, context) => voidcalled for non-object values (primitives, null, and_). Default: no-op. -
processCircular— a function(value, context) => voidcalled when an already-seen object is encountered. Default: no-op. -
registry— a flat array of Constructor/handler pairs:[Type1, handler1, Type2, handler2, ...]. When an object isinstanceofa registered type, the corresponding handler is called instead ofprocessObject. Default: the built-inregistryexport. -
filters— an array of predicate functions(value, context) => boolean. Called for objects that don't match the registry. Returntrueto indicate the value was fully handled. Default: empty. -
circular— a boolean flag to enable circular reference detection via aseenMap. -
symbols— a boolean flag. Whentrue, symbol properties are included. -
allProps— a boolean flag. Whentrue, non-enumerable properties are included. -
context— a custom context object. Its properties are preserved and available to all processors.
-
The walk function returns nothing. Results are collected through the context object (typically context.stackOut).
The following properties are set by walk on the context object and available to all processors:
-
stack— the processing stack. Push values here to schedule them for traversal. -
seen— aMaptracking visited objects (present whencircular: true, otherwisenull). -
symbols— reflects thesymbolsoption. -
allProps— reflects theallPropsoption.
Processors and registry handlers may set additional properties (e.g., stackOut, env).
The registry is a flat array iterated in steps of 2: [Type1, handler1, Type2, handler2, ...]. For each object encountered, if o instanceof Type, the corresponding handler (object, context) => void is called. The first match wins.
Extend with:
import {registry} from 'deep6/traverse/walk.js';
registry.push(MyClass, (val, context) => {
// handle MyClass instances
context.stackOut.push(val); // or push children to context.stack
});Filters are a plain array of predicate functions. They are checked for objects that don't match any registry entry. Each filter receives (object, context) and returns true if the value was handled.
Two factory functions create standard processors that handle post-processing:
-
processObject(postProcess, postProcessSeen)— returns a processor for plain objects and arrays. It pushes a Command onto the stack for post-processing, then pushes all property values.postProcessSeenis used instead ofpostProcesswhen circular tracking is active. -
processMap(postProcess, postProcessSeen)— same pattern for Maps. Pushes all Map values onto the stack.
The postProcess callbacks are called as methods on a Command, so this.s is the source object being processed.
Command is a deferred action pushed onto the walk stack. When popped, processCommand calls this.f(context). The this.s property holds the source object. This is how post-processing works: the command is pushed before the children, so it fires after all children have been processed.
import {Command, processCommand} from 'deep6/traverse/walk.js';
// Custom post-processor
function myPostProcess(context) {
const source = this.s;
// context.stackOut has processed children
}Circular is a marker wrapping an already-seen object. It is pushed to context.stackOut by the default processCircular. Post-processors check for Circular instances to resolve back-references.
All exports are named. The default export is walk.
-
walk— the main traversal function (alsowalk.Commandfor convenience). -
Command— deferred processing command class. -
Circular— marker class for circular references. -
registry— default type handler registry (flat array). -
filters— default filter array (initially empty). -
processOther— pushes value tocontext.stackOut. -
processCircular— pushesnew Circular(value)tocontext.stackOut. -
processObject(fn, fnSeen)— factory for object/array processors. -
processMap(fn, fnSeen)— factory for Map processors. -
processCommand— executes a Command's function. -
processVariable— resolves a Variable viacontext.envor passes it through. -
getObjectData(object, context)— returns{descriptors, keys}respectingsymbols/allPropsflags. -
buildNewObject(source, descriptors, keys, stackOut, wrap)— reconstructs an object from processed values onstackOut. -
buildNewMap(keys, stackOut, wrap)— reconstructs a Map from processed values onstackOut. -
postObjectCircular(source, descriptors, keys, context)— reconstructs an object with circular reference resolution. -
postMapCircular(source, context)— reconstructs a Map with circular reference resolution. -
setObject(seen, source, value)— resolves deferred circular reference actions. -
replaceObject(upTo, object, stackOut)— replacesstackOuttail with a single object (used when source is unchanged).
Built-in registry handles: Command, Array, Date, RegExp, Map, Set, URL, Promise, typed arrays, DataView, ArrayBuffer. Of these, only Array and Map actually recurse into children (via processObject/processMap); Date, RegExp, Set, Promise, URL, typed arrays, DataView, and ArrayBuffer are mapped to nop (no traversal). The default processObject handles plain objects (those not matched by the registry) and is what makes naked {...} and [...] recurse.
Cycle detection only fires for the recursing types — tracking atomic-leaf values would spuriously rewrite reused leaves as Circular placeholders on second visit. If a custom registry handler recurses into children (pushes onto context.stack), the type it handles is implicitly considered recursable.