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

easy-regex-lib

v0.1.3

Published

Semantic, composable, explainable regex patterns with TypeScript-first ergonomics

Readme

easy-regex-lib

Semantic patterns → real regex. Build composable, typed-friendly patterns from primitives (digits, letters, anchors, groups), compile them to JavaScript RegExp, and ship extras like plain-English explanations, JSON serialization, and lightweight safety hints.

This library is AST-first: everything compiles from an immutable pattern tree. The fluent API is sugar over that tree—so the same structure can power explanations, diagnostics, and tooling later.


Installation

npm install easy-regex-lib

Requires a modern JavaScript runtime with support for RegExp named capture groups if you use .named() / namedGroup() (Node 10+, modern browsers).


Regex coverage (evaluation)

No — easy-regex-lib does not model “all of regex.” It models a structured subset that maps cleanly to an AST and JavaScript RegExp, plus an escape hatch.

| Area | Status | Notes | | ------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Concatenation and alternation (OR) | Supported | Use seq, fluent .take, and alt. Compiler inserts (?:…) when precedence requires it. | | Quantifiers ?, *, +, {n}, {n,}, {n,m} | Supported | Greedy by default. Lazy via repeat(..., false) or optional(..., false). Helpers like digit().oneOrMore() are greedy only—use repeat for lazy. | | Literal text | Supported | literal(), .text() and .literal() on the builder; metacharacters escaped on emit. | | Start/end anchors ^ and $ | Supported | start(), end(), .start(), .end(). Meaning follows the m (multiline) flag like normal JS. | | Dot . | Supported | anyChar() maps to .; with dotAll (s) matches line breaks like JS. | | Digit, word, whitespace \d, \w, \s | Supported | digit(), word(), whitespace(). Digit and word use JS shorthands; exact sets follow the engine when u is set. | | Letters (ASCII / Unicode) | Partially structured | ASCII uses character classes. Unicode uses \p{L}, \p{Ll}, \p{Lu} when letter({ unicode: true }). Arbitrary \p{…} is not first-class—use raw(). | | Hex digits | Supported | hexDigit() maps to [0-9A-Fa-f] (or uppercase-only variant). | | Non-capturing groups (?:…) | Supported | nonCapturing(child). | | Named captures (?<name>…) | Supported | namedGroup, .named(). | | Numbered captures (…) | Not as a dedicated node | Use raw() if you need numbered groups, or prefer named groups. | | Custom brackets […] and negated [^…] | Not first-class | Compose with alt and literal for tiny sets; otherwise raw("[a-z_]+"). | | Word boundary \b | Supported | wordBoundary(), .boundary(). | | Flags g, i, m, s, u, y | Supported | Pass via compile / compilePattern flags; some nodes infer u for Unicode letters. | | Lookahead and lookbehind | Not first-class | (?=…), (?!…), (?<=…), (?<!…) via raw() (subject to JS engine rules). | | Backreferences \1, \k<name> | Not first-class | Use raw(). | | Atomic groups, possessive quantifiers | Not supported | JS RegExp has no native possessive or atomic syntax; raw() only if you target another engine later. | | Comments, verbose mode, recursion, conditionals | Not supported | PCRE-style or other-engine features—use raw() or another tool. | | “Full” RFC validators (email, URL, …) | Not guaranteed | Use presets or domain logic; regex alone is rarely enough for specs. |

Practical takeaway: use easy-regex-lib for readable structured patterns and tooling (explain / serialize). When you need arbitrary regex power, use raw() sparingly and treat it as unsafe-by-default (warnings + manual ReDoS review). You are still bound by JavaScript’s regex flavor regardless.


Basic examples (start here)

Digits only (whole string)

import { compile, digit, regex } from "easy-regex-lib";

const onlyDigits = regex().start().take(digit().oneOrMore()).end().compile();
onlyDigits.test("90210"); // true
onlyDigits.test("42a"); // false

Explanation: digit() is one “atom”; .oneOrMore() turns it into \d+. Wrapping with .start() / .end() makes the match cover the entire string (same idea as ^\d+$).


Literal text + optional digits

import { compile, digit, literal, optional, seq } from "easy-regex-lib";

const ast = seq(literal("id:"), optional(digit().oneOrMore()));
const p = compile(ast);
p.test("id:"); // true
p.test("id:99"); // true

Explanation: literal("id:") emits escaped text. optional(…) is the regex ? applied to \d+, so digits are allowed but not required after the prefix.


Either “yes” or “no” (case-sensitive)

import { alt, compile, literal } from "easy-regex-lib";

const p = compile(alt(literal("yes"), literal("no")));
p.test("yes"); // true
p.test("YES"); // false

Explanation: alt is alternation (|). There are no anchors here, so this regex matches substrings anywhere unless you add .start() / .end() or use ^…$ via anchors.


Case-insensitive match (flag)

import { compile, literal, regex } from "easy-regex-lib";

const p = regex()
  .start()
  .literal("hello")
  .end()
  .compile({ flags: { ignoreCase: true } });

p.test("HELLO"); // true

Explanation: Flags are not magic syntax—they’re normal RegExp flags. ignoreCase: true adds i.


One digit or one letter

import { alt, compile, digit, letter } from "easy-regex-lib";

const p = compile(alt(digit(), letter({ case: "both" })));
p.test("5"); // true
p.test("a"); // true
p.test("ab"); // false (only one character in this pattern)

Explanation: alt chooses between two single-character alternatives here. To allow longer runs, wrap repeats: e.g. digit().oneOrMore() vs letter().oneOrMore().


Single digit shortcut on the fluent builder

import { regex } from "easy-regex-lib";

const p = regex().start().digit().end().compile();
p.test("7"); // true
p.test("77"); // false

Explanation: .digit() appends exactly one \d (not “some digits”). Use .take(digit().oneOrMore()) when you need repetition.


Example 1 — Fluent builder (readable “starts with / then / ends with”)

import { digit, letter, regex } from "easy-regex-lib";

const pattern = regex()
  .start()
  .take(digit().exactly(3))
  .dash()
  .take(letter({ case: "upper" }).oneOrMore())
  .end()
  .compile();

pattern.source; // ^\d{3}-[A-Z]+$
pattern.test("123-ABC"); // true
pattern.test("123-ab"); // false

What this example shows

  • regex() / match() — Start a fluent MatchBuilder. Both names behave the same; pick whichever reads better in your codebase.
  • .start() / .end() — Map to ^ and $ so the match is anchored to the whole string (unless you change flags or omit anchors).
  • .take(...) — Accepts any composed pattern (AST node). Here we pass quantified primitives.
  • digit().exactly(3) — Three digits. Methods like .oneOrMore(), .maybe(), .between(min, max) work the same way on other primitives (letter, hexDigit, …).
  • .dash() — Inserts a literal - (escaped correctly at compile time).
  • .compile() — Produces a CompiledPattern: wrapper around the AST with .test(), .toRegExp(), .explain(), etc.

Why it helps

You express intent (“three digits, a dash, then uppercase letters”) without manually juggling escapes, quantifier precedence, or accidental capturing groups.


Example 2 — Functional composition (seq, alt, literal)

import { alt, compile, digit, letter, literal, seq } from "easy-regex-lib";

const ast = seq(
  literal("sku_"),
  digit().between(2, 5),
  literal("-"),
  alt(letter({ case: "upper" }), digit()),
);

const p = compile(ast);
p.test("sku_999-A"); // true

What this example shows

  • seq(...) — Concatenates fragments left-to-right (like implicit concatenation in regex).
  • alt(...) — Alternation (|). The compiler adds non-capturing groups when needed so precedence stays correct (for example (?:a|b)c, not ac|bc).
  • literal("...") — Raw text with meta-characters escaped on emit.
  • compile(ast) — Same end state as .compile() on the builder: a CompiledPattern from an arbitrary AST.

Why it helps

Fluent chains are great for linear shapes; seq / alt shine when patterns are data-driven (built from config), recursive, or generated by your own helpers.


Example 3 — Named capture groups

import { compile, digit, letter, match } from "easy-regex-lib";

const p = match()
  .start()
  .named("id", digit().oneOrMore())
  .literal("@")
  .named("host", letter({ case: "lower" }).oneOrMore())
  .end()
  .compile();

const m = p.exec("42@example");
console.log(m?.groups?.id); // "42"
console.log(m?.groups?.host); // "example"

What this example shows

  • .named(name, inner) — Emits (?<name>…) in modern JavaScript.
  • CompiledPattern.exec — Delegates to RegExp.prototype.exec, so groups works like native regex.

Why it helps

Named groups document what each slice means (“id”, “host”) instead of counting $1, $2. Downstream code stays readable and refactor-safe.


Example 4 — compilePattern vs compile (string + flags vs wrapper)

import { compilePattern, digit, regex } from "easy-regex-lib";

const ast = regex().start().take(digit().oneOrMore()).end().build();

const { pattern, flags, warnings } = compilePattern(ast);

console.log(pattern); // ^\d+$
console.log(flags); // ""
console.log(warnings); // e.g. notes about raw fragments if you used `raw()`

What this example shows

  • build() — Materializes the AST from a fluent builder without wrapping it.
  • compilePattern(ast) — Low-level: returns pattern (body string), flags string, and warnings (for example when raw() fragments appear).

Use compile(ast) when you want a CompiledPattern API; use compilePattern when integrating with libraries that only accept a pattern string and flags (OpenAPI pattern, legacy validators, etc.).


Example 5 — Plain-English explanation from the AST

import { explainPattern, literal, seq, digit } from "easy-regex-lib";

const ast = seq(literal("user_"), digit().oneOrMore());

const { clauses, summary } = explainPattern(ast);

console.log(summary);
// Matches the literal "user_". Repeat one or more times: Matches an ASCII digit.

console.log(clauses.map((c) => c.text));
// [ 'Matches the literal "user_".',
//   'Repeat one or more times: Matches an ASCII digit.' ]

What this example shows

  • explainPattern walks the same AST used for codegen and builds short natural-language clauses plus a summary string.
  • clauses — Structured lines you could feed to a UI, docs generator, or logs.

Why it helps

Regex errors are notoriously opaque. Explanations tied to structure give humans (and support teams) a second representation that stays in sync with what actually ships.


Example 6 — Diagnostics when a string fails (full-string check)

import { diagnose, regex, digit } from "easy-regex-lib";

const ast = regex().start().take(digit().exactly(3)).end().build();

console.log(diagnose(ast, "12"));
// { ok: false, index: 2, message: "...", expected: "..." }

console.log(diagnose(ast, "123"));
// { ok: true, match: "123", index: 0, groups: {} }

What this example shows

  • diagnose first tries an anchored full-string match (^(?:…)$ internally), then falls back to a linear simulation for many shapes to suggest where things went wrong.
  • Results include expected text derived from explanations (best-effort, not a full regex debugger).

Caveat

Lazy quantifiers, complex backtracking, and ambiguous alternatives can make simulation and the real engine disagree. Treat diagnose as a UX helper, not a formal verifier.


Example 7 — Lightweight safety hints (raw() and nested quantifiers)

import { analyzePattern, digit, raw, repeat, seq } from "easy-regex-lib";

const suspicious = seq(
  repeat(digit(), 0, Number.POSITIVE_INFINITY),
  raw("\\d+"),
);

console.log(analyzePattern(suspicious));
// Includes warnings/info about raw regex and nested quantifier shapes

What this example shows

  • raw("…") — Escape hatch: embed a regex fragment as-is. The compiler emits a warning because you bypass structural guarantees (including ReDoS reviews).
  • analyzePattern — Rule-based hints (not a full ReDoS solver). Use it to flag patterns worth human review.

Why it helps

Interop with existing regex snippets is unavoidable; pairing raw() with warnings keeps that path visible in audits and CI output.


Example 8 — JSON serialization (AI/tooling friendly)

import {
  deserializePattern,
  patternFromJsonString,
  patternToJsonString,
  presets,
  compilePattern,
} from "easy-regex-lib";

const ast = presets.slug();

const jsonText = patternToJsonString(ast, 2);
console.log(jsonText);

const roundTrip = deserializePattern(JSON.parse(jsonText));
console.log(compilePattern(roundTrip).pattern === compilePattern(ast).pattern); // true

What this example shows

  • patternToJsonString / serializePattern — Stable JSON with schemaVersion for forward compatibility.
  • deserializePattern / patternFromJsonString — Parse and validate payloads back into AST nodes.

Why it helps

Agents and services can exchange structured intent (JSON AST) instead of brittle plain-regex strings, then compile locally with your approved engine flags.


Example 9 — Presets (UUID-shaped, slug, hex color)

import { compile, presets } from "easy-regex-lib";

const uuid = compile(presets.uuid(), { flags: { ignoreCase: true } });
uuid.test("550e8400-e29b-41d4-a716-446655440000"); // true

const slug = compile(presets.slug());
slug.test("hello-world-2"); // true

const color = compile(presets.hexColor({ alpha: true }));
color.test("#abc");
color.test("#aabbcc");
color.test("#aabbccdd");

What each preset means (practically)

  • presets.uuid() — Hex digit runs with hyphen layout typical of UUID strings. It does not fully validate RFC 4122 variant/version bits; tighten rules if you need cryptographic guarantees.
  • presets.slug() — Opinionated ASCII slug: lowercase letters and digits, segments separated by single dashes.
  • presets.hexColor({ alpha: true })#RGB, #RRGGBB, and optional #RRGGBBAA when alpha is enabled.

Why presets exist

They encode product defaults once (tests + explanations), instead of copy-pasting fragile regex across services.


Example 10 — Unicode-aware letters (u flag inference)

import { compilePattern, letter } from "easy-regex-lib";

const { pattern, flags } = compilePattern(letter({ unicode: true }));
console.log(pattern); // \p{L}
console.log(flags); // includes "u"

What this example shows

  • Some nodes infer engine flags (here u for Unicode property escapes).
  • You can still pass compile({ flags: { … } }) to merge ignoreCase, multiline, etc.

Public API reference

Types below use short names; import types from the package as needed (Pattern, CompileOptions, …).

Constants

| Name | Returns | Description | | ------------------------ | -------- | ------------------------------------------------------------------------------------- | | PATTERN_SCHEMA_VERSION | number | Version baked into serializePattern / JSON payloads for forward-compatible tooling. |

AST helpers (combine & shape)

| Function | Returns | Description | | ---------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | literal(value) | Pattern | Literal string; metacharacters escaped when compiling. | | seq(...children) | Pattern | Concatenate fragments; flattens nested seq. | | alt(...children) | Pattern | Alternation (regex OR operator); flattens nested alt. | | repeat(child, min, max, greedy?) | Pattern | Bounded repetition; use Infinity for open-ended {n,}. Fourth arg greedy defaults to true; pass false for lazy quantifiers. Collapses min===max===1; maps (0,1) to optional. | | optional(child, greedy?) | Pattern | Optional child (? if greedy, ?? if lazy). | | namedGroup(name, child) | Pattern | Named capture (?<name>…) for JavaScript. | | nonCapturing(child) | Pattern | Non-capturing group (?:…) for grouping and precedence. | | optimize(pattern) | Pattern | Normalize tree (flatten seq and alt); used by compile and explain. |

Character-class-style primitives

Quantified helpers (digit, letter, …) return Quantified: a Pattern plus .exactly, .oneOrMore, .maybe, etc. Those chain methods compile to greedy quantifiers; use repeat / optional with greedy: false when you need lazy behavior.

| Function | Returns | Description | | ------------------- | ------------ | --------------------------------------------------------------------------------- | | digit(opts?) | Quantified | \d atom (see JS u for Unicode digit semantics). | | word(opts?) | Quantified | \w atom. | | whitespace(opts?) | Quantified | \s atom. | | letter(opts?) | Quantified | ASCII classes or \p{L} / \p{Ll} / \p{Lu} when unicode: true (infers u). | | hexDigit(opts?) | Quantified | [0-9A-Fa-f] or uppercase-only variant. | | anyChar() | Quantified | . (dot); respects s (dotAll) like normal RegExp. |

Anchors & boundaries

| Function | Returns | Description | | ---------------- | --------- | ----------- | | start() | Pattern | ^ | | end() | Pattern | $ | | wordBoundary() | Pattern | \b |

Small literals & composites

| Function | Returns | Description | | ------------------ | --------- | ------------------------------------------- | | dash() | Pattern | Literal -. | | underscore() | Pattern | Literal _. | | dot() | Pattern | Literal . (not “any character”). | | integer() | Pattern | One-or-more digits (digit().oneOrMore()). | | booleanLiteral() | Pattern | Alternates literals true and false. |

Escape hatch

| Function | Returns | Description | | --------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- | | raw(source, flags?) | Pattern | Inserts regex source verbatim; triggers compiler warnings; merges optional per-fragment flags into compile result. |

Compilation & execution

| Function | Returns | Description | | ------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ | | compile(ast, options?) | CompiledPattern | High-level wrapper: regex body, flags, .test, .exec, explain/diagnose/analyze, JSON helpers. | | compilePattern(ast, options?) | CompileResult | { pattern, flags, warnings } for interop (OpenAPI, validators, logging). | | toRegExp(ast, options?) | RegExp | Construct native RegExp without CompiledPattern. |

CompileOptions (second arg) includes flags (ignoreCase, multiline, dotAll, unicode, global, sticky) and nonCapturing (strip named captures for simulation-style use).

Understanding & safety

| Function | Returns | Description | | -------------------------------- | ------------------- | --------------------------------------------------------------------------------------------- | | explainPattern(ast) | ExplainResult | { clauses, summary } — human-oriented sentences from AST. | | diagnose(ast, input, options?) | DiagnoseResult | Full-string anchored check first; best-effort mismatch hints (not a complete regex debugger). | | analyzePattern(ast) | AnalysisFinding[] | Rule-based hints (e.g. raw(), nested quantifier shape); not a full ReDoS prover. |

Serialization

| Function | Returns | Description | | ---------------------------------- | ------------------- | ----------------------------------------------- | | serializePattern(ast) | SerializedPattern | { schemaVersion, pattern } JSON-ready object. | | deserializePattern(data) | Pattern | Validate + revive AST from unknown JSON input. | | patternToJsonString(ast, space?) | string | JSON.stringify of serializePattern. | | patternFromJsonString(text) | Pattern | Parse JSON text then deserialize. |

Fluent builder

| Function | Returns | Description | | -------------- | -------------- | ----------------------------------------------------- | | match(opts?) | MatchBuilder | Fluent API; optional default flags on .compile(). | | regex(opts?) | MatchBuilder | Alias of match. |

MatchBuilder methods

| Method | Returns | Description | | --------------------------------- | ----------------- | --------------------------------------------------------- | | .start() | this | Append ^. | | .end() | this | Append $. | | .boundary() | this | Append \b. | | .text(value), .literal(value) | this | Append escaped literal (same behavior). | | .dash() | this | Append -. | | .take(fragment) | this | Append any Pattern. | | .digit() | this | Append one \d (not quantified). | | .lettersUpper() | this | Append one ASCII A–Z. | | .lettersLower() | this | Append one ASCII a–z. | | .named(name, inner) | MatchBuilder | Append named group. | | .build() | Pattern | Materialize AST from chained fragments. | | .compile(options?) | CompiledPattern | Merge default flags + options, return executable wrapper. |

CompiledPattern members

| Member | Returns | Description | | ----------------------- | --------------------------- | ----------------------------------------------------------------- | | .ast | Pattern | Underlying AST reference. | | .source | string | Regex body (no delimiters). | | .flags | string | Concatenated flag letters (i, u, …). | | .warnings | CompileWarning[] | Compiler notices (e.g. raw fragments). | | .toRegExp() | RegExp | Fresh native regex instance. | | .test(input) | boolean | RegExp.prototype.test. | | .exec(input) | RegExpExecArray or null | Same as RegExp.prototype.exec (named groups when applicable). | | .explain() | ExplainResult | Same as explainPattern(ast). | | .diagnose(input) | DiagnoseResult | Same as diagnose(ast, input, opts). | | .analyze() | AnalysisFinding[] | Same as analyzePattern(ast). | | .toJSON() | SerializedPattern | Structured JSON object. | | .toJSONString(space?) | string | Pretty-printed JSON text. |

Presets

| Function | Returns | Description | | ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | | presets.uuid() | Pattern | UUID-shaped hyphenated hex (not full RFC 4122 semantics). | | presets.slug() | Pattern | Lowercase alphanumeric segments separated by -. | | presets.hexColor(opts?) | Pattern | Supports #RGB, #RRGGBB, and optional #RRGGBBAA when alpha is true; set short: false to disallow #RGB. |