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

pipsel

v0.0.5

Published

TypeScript DSL for extracting structured data from HTML

Readme

Pipsel

A Node.js and TypeScript DSL (Domain Specific Language) for extracting structured data from HTML. It uses a GraphQL-like hierarchical structure combined with Unix pipe-style value transformations.

Features

  • GraphQL-like Hierarchical Field Structure: Clean declarations for simple fields, nested objects, and arrays.
  • Unix-style Pipe Transformations: Chain extractors and transformers using |.
  • Built-in Linter & Formatter: Static analysis diagnostics and code formatting APIs and CLI commands.
  • Native ESM Support: Built for modern TypeScript/Node.js ES modules.

Installation

npm install pipsel

Pipsel DSL Specification

Pipsel syntax is JSON-like but optimized for HTML parsing.

1. Basic Fields

Extract elements and convert/clean values.

title: "h1" | text | trim

2. Optional Fields

Fields marked with ? are optional. If they cannot be found in the DOM (or evaluate to null), they are completely omitted/ignored from the final output JSON. Required fields (without ?) will remain in the output JSON with an explicit null value.

discount?: ".discount" | text | trim

3. Nullish Coalescing (Selector Fallbacks)

Resolve selectors sequentially. If a selector doesn't match any DOM elements, the engine falls back to the next selector.

title: "h1" ?? ".alternative-title" | text | trim

4. Fallbacks

Define custom values for cases where selectors don't match or evaluate to empty values.

title: "h1" | text | trim | fallback("Untitled Product")

5. List Blocks

Retrieve array items relative to parent selectors. List blocks can either extract arrays of nested objects:

products[]: ".product-card" {
  name: ".title" | text | trim
  price: ".price" | text | trim | float
  url: "a" | attr("href")
}

Or flat primitive list arrays (e.g., lists of strings, numbers, or booleans) directly by appending standard pipeline pipes:

tags[]: ".tags a" | text | trim | lowercase
prices[]: ".product-price" | text | float

6. Nested Lists

Scopes are recursively resolved.

categories[]: ".category" {
  name: "h2" | text | trim

  products[]: ".product" {
    title: ".title" | text | trim
    price: ".price" | text | trim | float
  }
}

7. Meta Variables

Assign current runtime parameters to fields without selectors:

  • @url: The URL currently being executed.
  • @timestamp: The ISO timestamp of the extraction execution.
  • @paginate: Placeholder meta parameter for pagination.
source_url: @url
extracted_at: @timestamp

8. Smart Naming Match Resolver

If a website uses dynamic/unpredictable CSS classes, or you want to write more resilient rules, use the smart semantic match resolver:

title: @match("title") | text | trim
price: @match("price") | text | float

The engine deterministically scores DOM elements looking for matches in id, class, data-testid, data-test, data-cy, aria-label, name, itemprop, and role attributes, including plural/synonym matches (e.g. price matches .amount or .cost).


Built-in Pipe Functions

| Pipe Function | Arguments | Description | |---|---|---| | text | None | Extract text content from the selected element. | | html | None | Extract outer/inner HTML content from the selected element. | | attr | (name: string) | Extract the value of the specified element attribute. | | trim | None | Trim whitespace around string values. | | trim_start / trim_end | None | Trim whitespace from the start or end of string values (aliases: trimStart, trimEnd). | | lowercase / lower | None | Convert string values to lowercase. | | uppercase / upper | None | Convert string values to uppercase. | | titlecase / title | None | Convert string values to Title Case. | | slugify | None | Convert string values into clean, URL-safe slugs. | | clean | None | Collapse multiple whitespace/newlines into a single space. | | prefix | (value: string) | Prepend prefix string value. | | suffix | (value: string) | Append suffix string value. | | substring / slice | (start: number, end?: number) | Extract portion of a string between start and end indices. | | replace | (from: string, to: string) | Replace all occurrences of from with to. | | regex | (pattern: string) | Match values against regex. Returns first capture group (or full match). | | split | (separator: string) | Split string values into arrays. | | int | None | Parse values into integers. | | float | None | Parse values into floating-point numbers. | | abs | None | Returns the absolute value of a number. | | round | (decimals?: number) | Rounds a number to specified decimal places (default 0). | | ceil / floor | None | Rounds a number up (ceil) or down (floor). | | add / subtract | (value: number) | Adds or subtracts a numeric value. | | multiply / divide | (value: number) | Multiplies or divides by a numeric value. | | min / max | None | Returns the minimum or maximum of a numeric array. | | sum / avg | None | Returns the sum or average of a numeric array (alias: average). | | bool / boolean | None | Converts values to booleans. Absent selector elements evaluate to false. | | fallback | (value: any) | Use specified fallback value if current value is null, undefined, or empty. | | filter | (pattern: string) | Filter items (arrays or strings) that match the regex pattern. | | url_parse / urlParse | None | Parse URL string into a structured object containing properties: href, protocol, hostname, port, pathname, search, hash, origin, and params (query parameters key-value object). | | url_protocol / urlProtocol | None | Extract the protocol (scheme) from a URL string (e.g., https:). | | url_hostname / urlHostname | None | Extract the hostname/domain from a URL string (e.g., example.com). | | url_port / urlPort | None | Extract the port number from a URL string. | | url_pathname / urlPathname / url_path / urlPath | None | Extract the path component from a URL string (e.g., /pathname). | | url_search / urlSearch / url_query / urlQuery | None | Extract the query string from a URL string (e.g., ?q=test). | | url_hash / urlHash | None | Extract the hash/fragment component from a URL string (e.g., #section). | | url_origin / urlOrigin | None | Extract the origin from a URL string (e.g., https://example.com). | | url_param / urlParam | (name: string) | Extract the value of the specified query parameter from a URL string. | | url_resolve / urlResolve / url_join / urlJoin | (base?: string) | Resolve a relative URL against a base URL (defaults to context @url). | | unique | (key?: string) | Retain unique values in an array. For primitive arrays, uses Set. For object arrays, checks uniqueness by the specified key. | | json_parse / jsonParse / json | None | Parse a JSON string into a structured JavaScript object or array. | | >, <, >=, <=, ==, =, != | (value: any) | Compare the pipeline value against the argument (numerically or lexically). Returns a boolean. | | required | (message?: string) | Throw a runtime validation error if the pipeline value is null, undefined, empty string, or an empty list. Supports custom error message. |


Programmatic API

You can parse, lint, and format DSL programs programmatically.

import { parse, format, lint } from "pipsel";

const dslSource = `
  title: "h1" | text | trim
  price?: ".price" | text | float
`;

// 1. Linting (Checks syntax, argument count, duplicate fields, etc.)
const diagnostics = lint(dslSource);
console.log(diagnostics); // [] (No issues)

// 2. Formatting (Pretty-prints DSL code)
const formatted = format(dslSource);
console.log(formatted);

// 3. Parsing (Produces an Abstract Syntax Tree)
const ast = parse(dslSource);
console.log(JSON.stringify(ast, null, 2));

// 4. Execution (Extracts structured data from HTML)
import { execute } from "pipsel";
const data = execute(ast, {
  html: "<h1>Hello World</h1>",
  url: "https://example.com"
});
console.log(data); // { title: "Hello World" }

Browser Page Execution (Playwright & Puppeteer)

Pipsel supports extracting structured data directly from headless browser pages using Playwright or Puppeteer. The pipsel(...) wrapper accepts Pages, Locators, and ElementHandles.

Playwright Integration

Page-level execution:
import { chromium } from "playwright";
import { pipsel } from "pipsel";

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");

// Run inline Pipsel script
const result = await pipsel(page).run(`
  title: "h1" | text | trim
`);
console.log(result); // { title: "Example Domain" }

await browser.close();
Relative Locator execution (returns array of results):
import { chromium } from "playwright";
import { pipsel } from "pipsel";

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com/products");

// Extract cards relative to locator
const cards = page.locator(".product-card");
const products = await pipsel(cards).run(`
  title: ".product-title" | text | trim
  price: ".price" | text | float
`);
console.log(products); // [ { title: "MacBook", price: 1999 }, ... ]

await browser.close();

Puppeteer Integration

Page-level execution:
import puppeteer from "puppeteer";
import { pipsel } from "pipsel";

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");

// Run from a rules file
const result = await pipsel(page).extract("product.psl");
console.log(result);

await browser.close();

CLI Usage

Pipsel includes a CLI to validate and format PSL rules from your shell:

Format a Rules File

Formats the .psl file in-place with normalized indentations and spacing.

pipsel fmt rules.psl

Lint a Rules File

Diagnoses any issues in the .psl script (exits with non-zero code on errors).

pipsel lint rules.psl

Explain a Rules File

Prints a visual tree representation of each field's pipeline in the .psl file.

pipsel explain rules.psl

Example output:

title
└── h1
    ├── text
    └── trim

price
└── .price
    ├── text
    ├── replace("$","")
    └── float

Development

Run unit tests:

npm run test

Build the package:

npm run build

License

MIT