JavaScript parser and stringifier for YAML documents with complete YAML 1.1 and 1.2 support
Visitor pattern implementation for traversing and transforming YAML ASTs. These functions provide powerful capabilities for analyzing, modifying, and processing YAML document structures programmatically.
Traverse YAML nodes synchronously with full control over the traversal process.
/**
* Visit nodes in a YAML tree synchronously
* @param node - Root node to start traversal
* @param visitor - Visitor function or object
*/
function visit(node: Node, visitor: visitor): void;
type visitor = visitorFn | { [key: string]: visitor };
type visitorFn<T = unknown> = (
key: number | string | null,
node: T,
path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T>;
// Control symbols for visitor functions
declare namespace visit {
/** Skip visiting children of current node */
const SKIP: unique symbol;
/** Stop traversal entirely */
const BREAK: unique symbol;
/** Remove current node from tree */
const REMOVE: unique symbol;
}Usage Examples:
import { parseDocument, visit, isScalar, isMap, isSeq } from "yaml";
const doc = parseDocument(`
config:
name: MyApp
version: "1.0.0"
features:
- authentication
- logging
- monitoring
database:
host: localhost
port: 5432
ssl: true
`);
// Basic traversal - log all nodes
visit(doc.contents, (key, node, path) => {
console.log(`Path: ${path.join('.')} | Key: ${key} | Type: ${typeof node}`);
if (isScalar(node)) {
console.log(` Scalar value: ${node.value}`);
}
});
// Find and collect all string values
const strings: string[] = [];
visit(doc.contents, (key, node) => {
if (isScalar(node) && typeof node.value === 'string') {
strings.push(node.value);
}
});
console.log('All strings:', strings);
// Transform values during traversal
visit(doc.contents, (key, node) => {
if (isScalar(node) && node.value === 'localhost') {
// Return modified scalar
return new Scalar('127.0.0.1');
}
if (isScalar(node) && typeof node.value === 'string' && node.value.startsWith('My')) {
// Transform string values
const newScalar = new Scalar(node.value.toUpperCase());
return newScalar;
}
});Traverse YAML nodes asynchronously, enabling I/O operations and async processing during traversal.
/**
* Visit nodes in a YAML tree asynchronously
* @param node - Root node to start traversal
* @param visitor - Async visitor function or object
*/
function visitAsync(node: Node, visitor: asyncVisitor): Promise<void>;
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };
type asyncVisitorFn<T = unknown> = (
key: number | string | null,
node: T,
path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;
// Control symbols for async visitor functions
declare namespace visitAsync {
/** Skip visiting children of current node */
const SKIP: unique symbol;
/** Stop traversal entirely */
const BREAK: unique symbol;
/** Remove current node from tree */
const REMOVE: unique symbol;
}Usage Examples:
import { parseDocument, visitAsync, isScalar, isMap } from "yaml";
const doc = parseDocument(`
users:
- id: 1
name: Alice
email: alice@example.com
- id: 2
name: Bob
email: bob@example.com
config:
api_endpoint: "https://api.example.com"
timeout: 30
`);
// Async processing with external API calls
async function validateEmails() {
await visitAsync(doc.contents, async (key, node, path) => {
if (isScalar(node) && typeof node.value === 'string' && node.value.includes('@')) {
console.log(`Validating email at ${path.join('.')}: ${node.value}`);
// Simulate async email validation
const isValid = await simulateEmailValidation(node.value);
if (!isValid) {
console.log(`Invalid email found: ${node.value}`);
// Could transform or mark for removal
return new Scalar(`INVALID:${node.value}`);
}
}
});
}
async function simulateEmailValidation(email: string): Promise<boolean> {
// Simulate async operation
return new Promise(resolve => {
setTimeout(() => {
resolve(email.includes('@') && email.includes('.'));
}, 100);
});
}
// Execute async traversal
await validateEmails();
// Async data enrichment
await visitAsync(doc.contents, async (key, node, path) => {
if (isScalar(node) && key === 'name' && typeof node.value === 'string') {
// Simulate fetching additional user data
const userData = await fetchUserData(node.value);
// Add data to parent if it's a map
const parentPath = path.slice(0, -1);
const parent = doc.getIn(parentPath, true);
if (isMap(parent)) {
parent.set('last_seen', userData.lastSeen);
parent.set('status', userData.status);
}
}
});
async function fetchUserData(name: string) {
// Simulate API call
return {
lastSeen: new Date().toISOString(),
status: 'active'
};
}Use visitor objects for complex traversal logic with different handlers for different paths.
// Visitor object type
type visitor = visitorFn | { [key: string]: visitor };
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };Usage Examples:
import { parseDocument, visit, isScalar, isSeq } from "yaml";
const doc = parseDocument(`
database:
connections:
- host: db1.example.com
port: 5432
- host: db2.example.com
port: 5432
settings:
timeout: 30
pool_size: 10
api:
endpoints:
- /users
- /products
rate_limit: 1000
`);
// Complex visitor object with different handlers
const configVisitor = {
// Handle database configuration
database: {
connections: (key, node) => {
console.log('Processing database connections');
if (isSeq(node)) {
console.log(`Found ${node.items.length} database connections`);
}
},
settings: {
timeout: (key, node) => {
if (isScalar(node) && typeof node.value === 'number') {
console.log(`Database timeout: ${node.value}s`);
// Warn about low timeouts
if (node.value < 10) {
console.warn('Database timeout is very low!');
}
}
}
}
},
// Handle API configuration
api: {
endpoints: (key, node) => {
if (isSeq(node)) {
console.log(`API has ${node.items.length} endpoints`);
node.items.forEach((endpoint, index) => {
if (isScalar(endpoint)) {
console.log(` ${index + 1}: ${endpoint.value}`);
}
});
}
},
rate_limit: (key, node) => {
if (isScalar(node) && typeof node.value === 'number') {
console.log(`API rate limit: ${node.value} requests`);
}
}
}
};
// Apply visitor object
visit(doc.contents, configVisitor);Control traversal behavior using special return symbols.
import { parseDocument, visit, isMap, isScalar } from "yaml";
const doc = parseDocument(`
public_config:
app_name: MyApp
version: 1.0.0
private_config:
api_key: secret123
database_password: supersecret
debug_info:
logs: enabled
trace: disabled
`);
// Skip sensitive sections
visit(doc.contents, (key, node, path) => {
// Skip private configuration entirely
if (key === 'private_config') {
console.log('Skipping private configuration');
return visit.SKIP; // Don't traverse children
}
// Stop processing after finding debug info
if (key === 'debug_info') {
console.log('Found debug section, stopping traversal');
return visit.BREAK; // Stop entire traversal
}
// Remove trace setting
if (key === 'trace') {
console.log('Removing trace setting');
return visit.REMOVE; // Remove this node
}
// Log other values
if (isScalar(node)) {
console.log(`${path.join('.')}.${key}: ${node.value}`);
}
});
console.log('Final document:');
console.log(doc.toString());Analyze document structure and collect statistics.
import { parseDocument, visit, isScalar, isMap, isSeq, isPair } from "yaml";
interface DocumentStats {
scalars: number;
maps: number;
sequences: number;
pairs: number;
maxDepth: number;
stringValues: string[];
numericValues: number[];
}
function analyzeDocument(doc: Document): DocumentStats {
const stats: DocumentStats = {
scalars: 0,
maps: 0,
sequences: 0,
pairs: 0,
maxDepth: 0,
stringValues: [],
numericValues: []
};
visit(doc.contents, (key, node, path) => {
// Track maximum depth
stats.maxDepth = Math.max(stats.maxDepth, path.length);
// Count node types
if (isScalar(node)) {
stats.scalars++;
if (typeof node.value === 'string') {
stats.stringValues.push(node.value);
} else if (typeof node.value === 'number') {
stats.numericValues.push(node.value);
}
}
else if (isMap(node)) {
stats.maps++;
}
else if (isSeq(node)) {
stats.sequences++;
}
else if (isPair(node)) {
stats.pairs++;
}
});
return stats;
}
// Analyze document
const doc = parseDocument(`
app:
name: MyApp
version: 1.0
features: [auth, logging]
users:
- name: Alice
age: 30
- name: Bob
age: 25
`);
const stats = analyzeDocument(doc);
console.log('Document statistics:', stats);// Core visitor types
type visitorFn<T = unknown> = (
key: number | string | null,
node: T,
path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T>;
type asyncVisitorFn<T = unknown> = (
key: number | string | null,
node: T,
path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;
// Visitor object types
type visitor = visitorFn | { [key: string]: visitor };
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };
// Control symbols
type VisitControlSymbol = typeof visit.SKIP | typeof visit.BREAK | typeof visit.REMOVE;
type AsyncVisitControlSymbol = typeof visitAsync.SKIP | typeof visitAsync.BREAK | typeof visitAsync.REMOVE;Install with Tessl CLI
npx tessl i tessl/npm-yaml