npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@glion/util-visit

v0.16.0

Published

An AST visitor for HL7v2 messages

Readme

@glion/util-visit

Visitor for traversing HL7v2 AST trees with ancestor context, depth, and HL7v2-aware sequence numbers.

What it does

Walks an HL7v2 AST from any starting node — Root, Segment, Field, Component, or Subcomponent — calling a visitor function for each matching node. Each call receives the node, its ancestors (root-to-parent), and a VisitInfo record with index, HL7v2 sequence number, depth, and extracted metadata. Supports filtering by type string, property object, or predicate, and exposes SKIP/EXIT control actions. Delegates core traversal to unist-util-visit-parents and pre-computes sibling indices for O(1) lookups.

Install

npm install @glion/util-visit

Use

import { visit, EXIT, SKIP } from "@glion/util-visit";
import { parse } from "@glion/parser";

const message = parse("MSH|^~\\&|...\rPID|...");

// Visit all segments
visit(message, "segment", (node, ancestors, info) => {
  console.log(`Segment: ${info.metadata?.header} at depth ${info.depth}`);
});

// Find fields with parent context
visit(message, "field", (node, ancestors, info) => {
  const segment = ancestors.find((n) => n.type === "segment");
  console.log(`Field ${info.sequence} in segment`);
});

// Skip processing of sensitive segments
visit(message, (node, ancestors, info) => {
  if (node.type === "segment" && info.metadata?.header === "NTE") {
    return SKIP;
  }
});

API

visit(tree, visitor)

visit(tree, test, visitor)

Visit nodes in an HL7v2 AST tree.

Parameters

  • tree (Nodes) — Tree to traverse. Can be any node type, not just Root.
  • test (string | Partial<Nodes> | Test, optional) — Filter:
    • string — Match nodes by type (e.g., "segment").
    • Partial<Nodes> — Match nodes with matching properties (e.g., { name: "PATIENT_GROUP" }).
    • Test — Custom function (node, ancestors) => boolean.
  • visitor (Visitor) — Function called for each matching node.

Returns

void.

Important: test vs visitor functions

If you pass a function as the second argument, it is always treated as a Visitor, never as a Test.

// WRONG — testFn will be treated as a visitor, not a test
visit(ast, (node) => node.type === 'segment', ...); // Missing visitor!

// CORRECT — Explicit 3-argument form
visit(ast, (node) => node.type === 'segment', (node, ancestors, info) => {
  console.log('Visiting segment');
});

// CORRECT — Use string or object for simple tests
visit(ast, 'segment', (node, ancestors, info) => {
  console.log('Visiting segment');
});

Visitor function

type Visitor<T extends Nodes = Nodes> = (
  node: T,
  ancestors: Nodes[],
  info: VisitInfo
) => VisitorResult;

The visitor receives:

  • node — Current AST node.
  • ancestors — Array of ancestor nodes from root to parent (not including current node).
  • info — Visit information with index, sequence, depth, and metadata.

The visitor can return:

  • undefined or void — Continue traversal normally.
  • SKIP — Skip children of current node.
  • EXIT — Stop traversal immediately.

VisitInfo

interface VisitInfo {
  /** 0-based index among siblings */
  index: number;

  /** 1-based sequence (HL7v2 convention). For segment-header: 0 */
  sequence: number;

  /** 1-based depth in tree (root = 1) */
  depth: number;

  /** Metadata (e.g., { header: "MSH" } or { name: "PATIENT" }) */
  metadata: Record<string, unknown> | undefined;
}

index and sequence represent the node's position in the tree, not its position among filtered results:

// Structure: MSH segment with fields at positions 1, 2, 3, 4
// Filter matches only field 3
visit(
  ast,
  (n) => n.type === "field" && hasContent(n),
  (node, ancestors, info) => {
    console.log(info.sequence); // 3 (position in segment, not "1st match")
  }
);

This is correct because HL7v2 paths like PID.3 refer to tree positions, not filtered positions.

Automatic metadata extraction

The metadata field is populated automatically:

| Node type | Metadata key | Description | | --------- | ------------ | ------------------------------------------- | | segment | header | Segment identifier (e.g., "MSH", "PID") | | group | name | Group name (e.g., "PATIENT_GROUP") |

Exports

import {
  visit, // Main traversal function
  EXIT, // Return to stop traversal
  SKIP, // Return to skip children
} from "@glion/util-visit";

import type {
  VisitInfo, // { index, sequence, depth, metadata }
  Visitor, // (node, ancestors, info) => VisitorResult
  VisitorResult, // Return type from visitor
  Test, // Filter predicate
  Predicate, // (node, ancestors) => boolean
} from "@glion/util-visit";

Examples

Filter by node type

visit(ast, "segment", (node, ancestors, info) => {
  console.log(`Found segment: ${info.metadata?.header}`);
});

Filter by properties

visit(ast, { name: "PATIENT_GROUP" }, (node, ancestors, info) => {
  console.log("Inside PATIENT_GROUP");
});

Custom test function

// Visit fields in MSH segment only
visit(
  ast,
  (node, ancestors) => {
    const parent = ancestors.at(-1);
    return node.type === "field" && parent?.type === "segment";
  },
  (node, ancestors, info) => {
    console.log(`Field at sequence ${info.sequence}`);
  }
);

Access parent and ancestors

visit(ast, "component", (node, ancestors, info) => {
  const parent = ancestors.at(-1);
  const segment = ancestors.findLast((n) => n.type === "segment");
  console.log(`Component at depth ${info.depth}`);
});

Control flow: skip children

visit(ast, (node, ancestors, info) => {
  if (node.type === "segment" && info.metadata?.header === "OBX") {
    return SKIP; // Don't process OBX segment children
  }
});

Control flow: exit early

import { EXIT } from "@glion/util-visit";

let found = false;
visit(ast, "field", (node, ancestors, info) => {
  if (/* some condition */) {
    found = true;
    return EXIT; // Stop traversal completely
  }
});

Start from any node

import { s, f, c } from "@glion/builder";

// Create a standalone segment
const segment = s("PID", f(c("value1")), f(c("value2")));

// Traverse from segment (not root)
visit(segment, "field", (node, ancestors, info) => {
  console.log(`Field at sequence ${info.sequence}`);
});

Track nesting levels

visit(ast, (node, ancestors, info) => {
  const indent = "  ".repeat(info.depth - 1);
  console.log(`${indent}${node.type} [${info.sequence}]`);
});
// root [1]
//   segment [1]
//     segment-header [0]
//     field [1]
//       field-repetition [1]
//         component [1]

Group hierarchy navigation

visit(ast, "segment", (node, ancestors, info) => {
  const groups = ancestors
    .filter((n) => n.type === "group")
    .map((n) => (n as any).name)
    .filter((name): name is string => typeof name === "string");

  console.log(`${info.metadata?.header} is in groups: ${groups.join(" > ")}`);
});
// PID is in groups: PATIENT_GROUP

Validate required fields

function validateRequiredFields(ast: Root): string[] {
  const errors: string[] = [];

  visit(ast, "segment", (node, ancestors, info) => {
    const segment = node as Segment;
    const header = info.metadata?.header;

    if (header === "MSH" && segment.children.length < 12) {
      errors.push("MSH segment missing required fields");
    }

    if (header === "PID") {
      const patientId = segment.children[3];
      if (!patientId || patientId.children.length === 0) {
        errors.push("PID segment missing required Patient ID (PID.3)");
      }
    }
  });

  return errors;
}

Extract data with context

interface PatientName {
  name: string;
  sequence: number;
  inGroup?: string;
}

function extractPatientNames(ast: Root): PatientName[] {
  const names: PatientName[] = [];

  visit(ast, "segment", (node, ancestors, info) => {
    if (info.metadata?.header !== "PID") return;

    const segment = node as Segment;
    const nameField = segment.children[5];
    if (nameField?.children[0]?.children[0]) {
      const nameComponent = nameField.children[0].children[0];
      const name = (nameComponent.children[0] as Subcomponent)?.value || "";
      const groupAncestor = ancestors.find((n) => n.type === "group");

      names.push({
        name,
        sequence: info.sequence,
        inGroup: groupAncestor ? (groupAncestor as any).name : undefined,
      });
    }
  });

  return names;
}

Find first match and exit

function findFirstObservation(ast: Root, targetCode: string): string | null {
  let result: string | null = null;

  visit(ast, "segment", (node, ancestors, info) => {
    if (info.metadata?.header !== "OBX") return;

    const segment = node as Segment;
    const identifierField = segment.children[3];
    const code = identifierField?.children[0]?.children[0]?.children[0]?.value;

    if (code === targetCode) {
      const valueField = segment.children[5];
      result = valueField?.children[0]?.children[0]?.children[0]?.value || null;
      return EXIT;
    }
  });

  return result;
}

Part of Glion

@glion/util-visit is part of Glion, the application framework for HL7v2. See the Glion README for the full package catalog and architecture.