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-query

v0.15.3

Published

Query HL7v2 AST nodes by HL7 location paths

Downloads

1,095

Readme

@glion/util-query

Reads HL7v2 ASTs using canonical paths like PID-5[1].2.1 or ORDER-ORC-1.

What it does

Provides six path-based helpers — parse, select, selectAll, value, matches, format — that operate on HL7v2 AST trees using the same path syntax found in HL7 specs. Paths can address segments (PID), groups (ORDER), nested groups and segments (ORDER-TIMING-TQ1), field repetitions (PID-5[2]), components (PID-5[1].2), and subcomponents (PID-5[1].2.1). Path parsing is memoized in an LRU cache so repeated lookups stay fast.

Install

npm install @glion/util-query

Use

import {
  select,
  selectAll,
  value,
  matches,
  format,
  parse,
} from "@glion/util-query";
import { parseHL7v2 } from "@glion/parser";

const message = `MSH|^~\\&|MyApp|MyFacility|ReceivingApp|ReceivingFacility|20231201120000||ADT^A01|12345|P|2.5
PID|1||123456789^^^MRN||Smith^John^Michael||19800101|M|||123 Main St^^Anytown^ST^12345
OBX|1||8675-3^Glucose^LN||120|mg/dL|70-105|H|||F`;

const ast = parseHL7v2(message);

// Values: drills to subcomponents automatically when the path is unique
const lastName = value(ast, "PID-5[1].1.1"); // "Smith"
const sendingApp = value(ast, "MSH-3[1].1.1"); // "MyApp"

// Single node: grab the first matching AST node
const pidSegment = select(ast, "PID");
if (pidSegment?.node.type === "segment") {
  console.log(`PID has ${pidSegment.node.children.length - 1} fields`);
}

// Multiple nodes
const allObservations = selectAll(ast, "OBX");
for (const { node } of allObservations) {
  console.log(`Found observation: ${node.type}`);
}

// Existence check
if (matches(ast, "PID-5")) {
  console.log("Patient has a name field");
}

// Path parsing
const parsed = parse("ORDERS[2]-OBX-5[1].2.1");

API

parse(path: string): PathParts

Breaks a canonical HL7 path into structured pieces. Useful for tooling and diagnostics.

parse("PID-5[1].2.1");
// {
//   segment: { name: 'PID' },
//   field: 5,
//   repetition: 1,
//   component: 2,
//   subcomponent: 1
// }

parse memoizes up to 1,000 unique paths using an LRU cache so repeated lookups stay fast without leaking memory.

select<Path>(root: Root, path: Path): { node: InferNodeType<Path>; ancestors: Nodes[] } | null

Returns the first AST node addressed by the path, along with its ancestor chain, or null when it cannot be found. The return type is automatically inferred from the path string.

const result = select(ast, "PID-5[1].2");
if (result) {
  result.node; // Type: Component
  result.ancestors; // [Root, Segment, Field, FieldRepetition]
}

The ancestors array follows the unist-util-visit-parents convention: it starts at the root node, ends with the immediate parent, and never includes the node itself. You can rely on ancestors[ancestors.length - 1] being the direct parent.

selectAll<Path>(root: Root, path: Path): Array<{ node: InferNodeType<Path>; ancestors: Nodes[] }>

Returns all AST nodes that match the path. Useful when a message contains multiple segments or groups of the same type.

// Get all OBX segments
const observations = selectAll(ast, "OBX");
for (const { node } of observations) {
  console.log(node.type); // 'segment'
}

// Get all ORDER groups
const orders = selectAll(ast, "ORDER");
for (const { node } of orders) {
  console.log(node.type); // 'group'
}

// Get all observation values
const values = selectAll(ast, "OBX-5");
for (const { node } of values) {
  console.log(node.type); // 'field'
}

value(root: Root, path: string): { value: string; node: Nodes; ancestors: Nodes[] } | null

Returns the string value stored at the path. If the node is not a subcomponent, it walks through single-child layers (field → field repetition → component → subcomponent) automatically.

const result = value(ast, "PID-3[1].1.1");
if (result) {
  console.log(result.value); // "123456789"
  console.log(result.node.type); // "subcomponent"
  console.log(result.ancestors); // [Root, Segment, Field, ...]
}

matches(root: Root, path: string): boolean

Returns true when the path points to an existing node, otherwise false.

if (!matches(ast, "OBX-5")) {
  throw new Error("Missing observation value");
}

if (matches(ast, "PID-5")) {
  const name = value(ast, "PID-5.1.1");
}

format(node: Nodes, ancestors: Nodes[]): string | null

The inverse of select. Given a node and its ancestor chain (as returned by visit or select), produces the canonical path string that would select that node. Returns null if the chain contains no segment or group.

// With select
const result = select(ast, "PID-5.2");
if (result) {
  format(result.node, result.ancestors); // "PID-5.2"
}

// With visit
visit(ast, "field", (node, ancestors) => {
  const path = format(node, ancestors); // "PID-5", "OBX-1", etc.
  console.log(`Visiting ${path}`);
});

// Round-trip: format(select(root, path)) === path

clearParseCache(): void

Clears the memoized parse results. Useful for long-running services that want to release memory during idle periods or before reloading configuration.

getParseCacheSize(): number

Returns the current number of cached path entries so you can monitor cache pressure.

Types

type PathParts = {
  groups?: Array<{ name: string; repetition?: number }>;
  segment: { name: string; repetition?: number };
  field?: number;
  repetition?: number;
  component?: number;
  subcomponent?: number;
};

Path format

Canonical path grammar:

  • SEGMENT — segment (e.g., PID)
  • SEGMENT-FIELD — field (e.g., PID-5)
  • SEGMENT-FIELD[REP] — field repetition (e.g., PID-5[2])
  • SEGMENT-FIELD[REP].COMP — component (e.g., PID-5[1].2)
  • SEGMENT-FIELD[REP].COMP.SUB — subcomponent (e.g., PID-5[1].2.1)
  • GROUP[N]-... — qualify with group name and optional repetition (e.g., ORDERS[2]-OBX-5)

Repetition indexes are 1-based. If you omit [n], select/value/matches assume [1].

Paths select whatever exists in the AST — segments or groups. A bare name (NAME) returns whichever exists, trying segments first; adding a field suffix (NAME-N) forces segment access because field numbers only apply to segments. Groups can also appear as navigation prefixes (ORDER-ORC, ORDER-TIMING-TQ1-1).

// Selecting groups directly
const orderGroup = select(ast, "ORDER");
if (orderGroup?.node.type === "group") {
  console.log("Found ORDER group");
}

// Selecting segments
const pid = select(ast, "PID");
if (pid?.node.type === "segment") {
  console.log("Found PID segment");
}

// Navigation through groups to segments
value(ast, "ORDER-ORC-1"); // Order control
value(ast, "ORDER-TIMING-TQ1-1"); // Timing quantity

Common recipes

Patient demographics

const lastName = value(ast, "PID-5[1].1.1")?.value;
const firstName = value(ast, "PID-5[1].2.1")?.value;
const middleName = value(ast, "PID-5[1].3.1")?.value;
const dob = value(ast, "PID-7[1].1.1")?.value;

Observations

// Single observation
const obsValue = value(ast, "OBX-5[1].1.1")?.value;
const units = value(ast, "OBX-6[1].1.1")?.value;
const refRange = value(ast, "OBX-7[1].1.1")?.value;

// Multiple observations
const allOBX = selectAll(ast, "OBX");
for (const { node } of allOBX) {
  const val = value(node, "OBX-5")?.value;
  const u = value(node, "OBX-6")?.value;
  console.log(`${val} ${u}`);
}

Validation

if (!matches(ast, "MSH")) throw new Error("Missing message header");
if (!matches(ast, "PID-3[1].1.1")) throw new Error("Missing patient ID");

if (matches(ast, "OBX")) {
  const observations = selectAll(ast, "OBX");
  console.log(`Found ${observations.length} observations`);
}

Error messages

  • Invalid formats throw with guidance, e.g. "Invalid HL7 path format: \"PID-\"".
  • Out-of-range indexes throw, e.g. "Field number must be ≥1, got: 0".
  • Whitespace is rejected to catch accidental typos.

Part of Glion

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