A powerful TypeScript library for traversing and manipulating any data structure with fine-grained flow control. Perfect for data transformation, recursive processing, and any scenario requiring deep data structure traversal.
- 🗂️ Universal Traversal: Visit all object properties and array elements recursively
- 🔄 Flow Control: CONTINUE, BREAK, EXIT, STEP_OVER operations
- ✏️ Data Manipulation: REPLACE and DELETE values during traversal
- đź”— Parent Tracking: Access parent information and property keys during traversal
- ⚡ Async Support: Both synchronous and asynchronous traversal
- 🦕 Deno Ready: Built for Deno with JSR compatibility
- 🏷️ Type Safe: Full TypeScript support with advanced type inference
# Using Deno
import { visit, CONTINUE, REPLACE } from "jsr:@luma-dev/visit-ts";
# Using JSR with other runtimes
npx jsr add @luma-dev/visit-tsimport { CONTINUE, DELETE, REPLACE, visit } from "jsr:@luma-dev/visit-ts";
// Any data structure
const data = {
name: "users",
items: [
{ name: "Alice", age: 30, active: true },
{ name: "Bob", age: 25, active: false },
],
config: { version: "1.0", debug: true },
};
// Transform data during traversal
visit(data, (value) => {
// Increment all ages
if (typeof value === "number" && value > 18 && value < 100) {
return REPLACE(value + 1, CONTINUE);
}
// Remove debug flag
if (value === true && typeof value === "boolean") {
// This will find debug: true and remove it
return DELETE;
}
return CONTINUE;
});
console.log(data);
// Output: { name: "users", items: [{ name: "Alice", age: 31, active: true }, { name: "Bob", age: 26, active: false }], config: { version: "1.0" } }Continue normal traversal to nested values:
visit(data, (value) => {
console.log("Visiting:", value);
return CONTINUE; // Visit nested properties and array elements
});Skip visiting nested properties/elements of the current value:
visit(data, (value) => {
if (typeof value === "object" && value?.type === "private") {
return STEP_OVER; // Don't visit properties of private objects
}
return CONTINUE;
});Stop processing siblings at the current level:
visit(data, (value) => {
if (value === "stop") {
return BREAK; // Stop processing other properties/elements at this level
}
return CONTINUE;
});Immediately exit the entire traversal:
visit(data, (value) => {
if (value === "error") {
return EXIT; // Stop everything
}
return CONTINUE;
});Replace any value with a new value:
visit(data, (value) => {
if (typeof value === "string" && value.startsWith("old_")) {
return REPLACE(
value.replace("old_", "new_"),
CONTINUE, // What to do after replacement
);
}
return CONTINUE;
});Remove any property or array element:
visit(data, (value) => {
if (value === "remove-me") {
return DELETE; // Removes the property or array element containing this value
}
return CONTINUE;
});You can combine DELETE with flow control:
import { DELETE_BREAK, DELETE_EXIT } from "jsr:@luma-dev/visit-ts";
visit(data, (value) => {
if (value === "remove-and-break") {
return DELETE_BREAK; // Delete and stop processing siblings
}
if (value === "remove-and-exit") {
return DELETE_EXIT; // Delete and exit traversal
}
return CONTINUE;
});Access parent objects and property keys during traversal:
visit(data, (value, parents) => {
if (parents.length > 0) {
const immediate_parent = parents[parents.length - 1];
console.log(
`Value ${value} is at key '${immediate_parent.key}' in parent:`,
immediate_parent.node,
);
}
return CONTINUE;
});For asynchronous operations during traversal:
import { visitAsync } from "jsr:@luma-dev/visit-ts";
await visitAsync(data, async (value) => {
if (typeof value === "string" && value.startsWith("http")) {
const response = await fetch(value);
const data = await response.json();
return REPLACE(data, CONTINUE);
}
return CONTINUE;
});Complex data transformation with multiple operations:
const userData = {
users: [
{
name: "Alice",
email: "old_alice@example.com",
settings: { theme: "dark", notifications: true },
},
{
name: "Bob",
email: "old_bob@example.com",
settings: { theme: "light", notifications: false },
},
],
metadata: {
version: "1.0.0",
deprecated: true,
lastUpdate: "2024-01-01",
},
};
visit(userData, (value, parents) => {
// Update email domains
if (typeof value === "string" && value.includes("old_")) {
return REPLACE(value.replace("old_", "new_"), CONTINUE);
}
// Remove deprecated flags
if (value === true && parents.length > 0) {
const parent = parents[parents.length - 1];
if (parent.key === "deprecated") {
return DELETE;
}
}
// Convert theme settings to numbers
if (value === "dark") {
return REPLACE(1, CONTINUE);
}
if (value === "light") {
return REPLACE(0, CONTINUE);
}
return CONTINUE;
});The library provides excellent TypeScript support with recursive type inference:
type MyDataStructure = {
id: string;
items: { name: string; count: number }[];
config?: { enabled: boolean };
};
const data: MyDataStructure = {
id: "test",
items: [{ name: "item1", count: 5 }],
config: { enabled: true },
};
// TypeScript automatically infers the correct value types
visit(data, (value) => {
// `value` includes all possible types in the data structure (see limitations)
if (typeof value === "string") {
console.log("String value:", value);
}
if (typeof value === "number") {
console.log("Number value:", value);
}
return CONTINUE;
});visit<T>(data: T, visitor: VisitorFunction): void- Synchronous data structure traversalvisitAsync<T>(data: T, visitor: AsyncVisitorFunction): Promise<void>- Asynchronous data structure traversal
CONTINUE- Continue to nested properties/elementsSTEP_OVER- Skip nested properties/elementsBREAK- Stop processing siblingsEXIT- Exit entire traversalDELETE- Delete property/element and continueDELETE_BREAK- Delete property/element and breakDELETE_EXIT- Delete property/element and exit
REPLACE(value: unknown, then: FlowControl)- Replace value with new value
The library uses recursive TypeScript types to infer all possible value types in your data structure. Type inference is limited to approximately 10 levels of nesting. Beyond this depth:
- Runtime traversal continues to work correctly at any depth
- TypeScript may not infer the exact types for deeply nested values
- The visitor function parameter will fall back to
unknowntype for very deep structures
Example:
// Types are fully inferred up to ~10 levels deep
const deepData = {
level1: {
level2: {
level3: {
// ... up to level10: still typed correctly
level10: {
level11: "deeply nested", // May be typed as 'unknown'
},
},
},
},
};This limitation only affects TypeScript's static type checking - the runtime behavior remains fully functional at any nesting depth.
# Run tests
deno test
# Format code
deno fmt
# Type check
deno check mod.ts
# Lint
deno lintCC0-1.0 - Public Domain