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

indent-xpath

v0.2.0

Published

Generic-purpose XPath-like node selector for tree-shaped data (kind/value/attrs/children), usable standalone or from a language server.

Readme

indent-xpath

Generic-purpose, XPath-like node selector for tree-shaped data (kind / value / attrs / children). Usable standalone on any tree with this shape, and consumed by indent-language-server (via an adapter) for resolving ref-typed attribute values. Zero runtime dependencies, and no dependency (runtime or dev) on indent-parser -- it operates on a structurally compatible XPathNode shape declared independently.

This package implements a subset of XPath, not the full grammar. See "Not supported" below for what's intentionally left out.

Supported syntax

//api                       // descendant: match anywhere in the tree, any depth
//service//endpoint         // mid-path descendant: any depth under a matched service
/org/team/service           // absolute path: from the tree root, one level per step
//service[owner]              // predicate: attribute exists
//service[owner="ateam"]      // predicate: attribute equals a quoted string
//service[port=8080]          // predicate: attribute equals an unquoted number
//service[active=true]        // predicate: attribute equals an unquoted boolean
//service[owner="ateam",port=8080]     // multi-predicate: comma-separated, ANDed together
//service[.="Auth Service"]            // self-axis: match the node's own positional `value`
  • Steps (/name or //name) match a node's kind, case-insensitively.
  • / (child axis) descends exactly one level.
  • // (descendant axis) matches at any depth, including zero -- so a leading //api also matches an api node at the root, and mid-path //service//endpoint matches an endpoint at any depth under any previously-matched service.
  • [prop] checks the node's attrs for key existence.
  • [prop=value] checks the node's attrs[prop] for equality against a typed value.
  • [prop1=v1,prop2=v2] ANDs multiple attribute equality checks together in one bracket, comma-separated. Bare existence checks ([prop]) are only allowed on their own -- mixing one into a comma list (e.g. [owner,port=8080]) is a parse error; every clause in a multi-clause list must be name=value.
  • [.=value] checks the node's own positional value field (the thing an Indent line like service "Auth Service" carries before any attributes) -- reuses XPath's self-axis (.) convention.
  • Predicate values follow the same literal typing rules as Indent itself:
    • "..." (quoted) is always a string, e.g. [owner="ateam"].
    • Unquoted true / false is a boolean.
    • Unquoted numeric text matching /^-?\d+(\.\d+)?$/ is a number.
    • Anything else unquoted is a parse error.
    • \" inside a quoted value is an escaped quote.
    • A , or / inside a quoted value doesn't split a predicate list or the path -- the scanner is quote-aware.
  • Equality is strict-typed, not stringified: [port="8080"] (quoted string) does not match an attribute stored as the number 8080.

Not supported

This subset deliberately omits parts of the existing indent-language-server xpath grammar (which is coupled to its own indexed-node model). Using any of these throws XPathParseError:

  • Relative paths without a leading / (e.g. bare service/api).
  • .. (parent axis).
  • @name as a standalone path step (attribute axis outside of a predicate).
  • Wildcard * names.
  • Positional index predicates, e.g. [0].
  • Multiple separate bracket groups on one step, e.g. [a][b] -- use a single comma-separated bracket instead: [a,b].
  • A name=value shorthand for the self-value match is not supported -- use the bracketed [.=value] form instead.
  • A field-projection/selection syntax (e.g. returning just one attribute's value instead of the whole node) is not implemented. selectNodes always returns full XPathNodes; read whatever field you need off the result yourself (node.attrs?.owner, node.value, etc).

API

import { parseXPath, selectNodes, XPathParseError } from "indent-xpath";
import type { XPathNode, ParsedXPath } from "indent-xpath";

const tree: XPathNode[] = [
  {
    kind: "service",
    value: "Auth Service",
    attrs: { owner: "ateam", port: 8080 },
    children: [{ kind: "api", value: "Login", attrs: { method: "POST" } }],
  },
];

selectNodes(tree, "//api"); // -> [ { kind: "api", value: "Login", ... } ]
selectNodes(tree, '/service[owner="ateam"]'); // -> [ the service node ]

// Parse once, evaluate many times against different trees:
const parsed: ParsedXPath = parseXPath("//service//api");
selectNodes(tree, parsed);

XPathNode is a minimal shape (kind, optional value, optional attrs, optional children) -- indent-parser's IndentNode is structurally assignable to it, so it can be passed directly to selectNodes without any adapter, despite there being no dependency between the two packages.

parseXPath and selectNodes throw XPathParseError (with a raw property holding the original expression) on malformed or unsupported input.

Development

npm install
npm run build
npm test