Documentation

Everything you need to write regex like a normal person. No PhD required.

#Quick Start

# npm
npm install zeroreg

# pnpm
pnpm add zeroreg

# yarn
yarn add zeroreg

# bun
bun add zeroreg

Your first pattern

import { digit, literal } from 'zeroreg'

// Match a phone number: 123-456-7890
const phone = digit(3).then('-').then(digit(3)).then('-').then(digit(4))

phone.test('123-456-7890')  // true
phone.toRegex()             // /\d{3}-\d{3}-\d{4}/

Or use pre-built patterns

import { email, url, phone } from 'zeroreg/patterns'

email.test('hello@world.com')     // true
url.test('https://github.com')    // true
phone.test('+1-234-567-8900')     // true

# Core Concepts

zeroReg is built on one simple idea: regex should read like English. Every function returns a Pattern object that you can chain, combine, and convert to native RegExp.

The Pattern Object

Every builder function returns a Pattern. Patterns are immutable and chainable:

const pattern = digit()      // Create a pattern
  .oneOrMore()                // Chain quantifier
  .then('-')                  // Chain more patterns
  .then(letter().times(3))    // Nest patterns

// Convert to RegExp when ready
const regex = pattern.toRegex()  // /\d+-[a-zA-Z]{3}/

# Character Classes

Match specific types of characters.

FunctionDescriptionRegex
digit()Any digit 0-9\\d
digit(n)Exactly n digits\\d{n}
nonDigit()Any non-digit\\D
word()Word char (a-z, 0-9, _)\\w
nonWord()Non-word char\\W
letter()Any letter a-z, A-Z[a-zA-Z]
lowercase()Lowercase letter[a-z]
uppercase()Uppercase letter[A-Z]
alphanumeric()Letter or digit[a-zA-Z0-9]
whitespace()Whitespace\\s
nonWhitespace()Non-whitespace\\S
any()Any character.
literal(str)Exact match (escaped)str
charIn('abc')Any char in set[abc]
charNotIn('abc')Any char NOT in set[^abc]
range('a', 'z')Character range[a-z]

Examples

import { digit, letter, charIn, literal } from 'zeroreg'

// Match exactly 3 digits
digit(3).test('123')  // true

// Match a vowel
charIn('aeiou').test('e')  // true

// Match a literal dot (escaped automatically)
literal('.').test('.')  // true
literal('.').test('a')  // false

# Quantifiers

Control how many times a pattern matches.

MethodDescriptionRegex
.oneOrMore()1 or more+
.zeroOrMore()0 or more*
.optional()0 or 1?
.times(n)Exactly n times{n}
.between(min, max)Between min and max{min,max}
.atLeast(n)n or more times{n,}
.atMost(n)0 to n times{0,n}

Examples

import { digit, letter, optional } from 'zeroreg'

// One or more digits
digit().oneOrMore().test('123')  // true

// Optional plus sign, then digits
optional('+').then(digit().oneOrMore()).test('+123')  // true
optional('+').then(digit().oneOrMore()).test('123')   // true

// Between 2 and 4 letters
letter().between(2, 4).test('abc')  // true

# Groups

Capture parts of your match or group patterns together.

FunctionDescriptionRegex
capture(pattern)Capturing group(...)
capture(pattern, 'name')Named capture(?<name>...)
group(pattern)Non-capturing group(?:...)
oneOf(a, b, c)Match any of(?:a|b|c)

Examples

import { capture, digit, oneOf, group } from 'zeroreg'

// Extract year, month, day
const datePattern = capture(digit(4), 'year')
  .then('-')
  .then(capture(digit(2), 'month'))
  .then('-')
  .then(capture(digit(2), 'day'))

const match = '2024-03-15'.match(datePattern.toRegex())
match.groups.year   // '2024'
match.groups.month  // '03'
match.groups.day    // '15'

// Match "cat" or "dog" or "bird"
oneOf('cat', 'dog', 'bird').test('dog')  // true

// Group without capturing (for quantifiers)
group(literal('ab').or('cd')).oneOrMore().test('abcdab')  // true

# Anchors

Match positions in the string, not characters.

FunctionDescriptionRegex
startOfLine()Start of string^
endOfLine()End of string$
wordBoundary()Word boundary\\b
nonWordBoundary()Non-word boundary\\B

Examples

import { startOfLine, endOfLine, literal, digit, wordBoundary } from 'zeroreg'

// Match "hello" at start of string
startOfLine().then(literal('hello')).test('hello world')  // true
startOfLine().then(literal('hello')).test('say hello')    // false

// Match exactly 3 digits (nothing more)
startOfLine().then(digit(3)).then(endOfLine()).test('123')   // true
startOfLine().then(digit(3)).then(endOfLine()).test('1234')  // false

// Match whole word "cat" (not "category")
wordBoundary().then(literal('cat')).then(wordBoundary())
  .toRegex().test('the cat sat')  // true

# Lookahead & Lookbehind

Assert what comes before or after without including it in the match.

FunctionDescriptionRegex
lookahead(pattern)Followed by(?=...)
negativeLookahead(pattern)NOT followed by(?!...)
lookbehind(pattern)Preceded by(?<=...)
negativeLookbehind(pattern)NOT preceded by(?<!...)

Examples

import { digit, lookahead, lookbehind, literal } from 'zeroreg'

// Match digits followed by "px"
digit().oneOrMore().then(lookahead(literal('px')))
  .toRegex().exec('100px')  // ['100']

// Match digits preceded by "$"
lookbehind(literal('$')).then(digit().oneOrMore())
  .toRegex().exec('$500')  // ['500']

# Chaining with .then()

The .then() method lets you chain patterns together sequentially.

import { digit, literal, letter, optional } from 'zeroreg'

// Build complex patterns step by step
const productCode = literal('PRD')
  .then('-')
  .then(digit(4))
  .then('-')
  .then(letter().times(2))

productCode.test('PRD-1234-AB')  // true
productCode.toRegex()            // /PRD-\d{4}-[a-zA-Z]{2}/

// .then() accepts strings (auto-escaped) or patterns
digit(3).then('.').then(digit(3))  // Matches "123.456"

# Output Methods

Convert your pattern to different formats and use it.

MethodReturns
.toRegex(flags?)Native RegExp object
.test(string)boolean — does it match?
.match(string)First match or null
.matchAll(string)Array of all matches
.replace(string, replacement)String with replacements
.toString()Regex pattern string

Examples

import { digit } from 'zeroreg'

const pattern = digit().oneOrMore()

// Test if string matches
pattern.test('abc123')  // true

// Get first match
pattern.match('abc 123 def 456')  // ['123']

// Get all matches
pattern.matchAll('abc 123 def 456')  // [['123'], ['456']]

// Replace all matches
pattern.replace('abc 123 def 456', 'X')  // 'abc X def X'

// Get native RegExp with flags
pattern.toRegex('gi')  // /\d+/gi

// Get pattern string
pattern.toString()  // '\d+'

# Pre-built Patterns

Import ready-to-use patterns for common use cases.

import { email, url, phone, date, uuid, ... } from 'zeroreg/patterns'
email

user@domain.com

url

https://example.com

phone

+1-234-567-8900

date

2024-03-15

time

14:30:00

ipv4

192.168.1.1

ipv6

2001:0db8:...

uuid

550e8400-e29b-...

hexColor

#ff6600

hex

a1b2c3

slug

my-post-title

hashtag

#trending

mention

@username

creditCard

4111111111111111

ssn

123-45-6789

zipCode

12345-6789

username

user_123

strongPassword

MyP@ssw0rd

semver

1.2.3-alpha

macAddress

00:1A:2B:3C:4D:5E

# Real World Examples

Phone Number with Optional Country Code

import { digit, optional } from 'zeroreg'

const phone = optional('+')
  .then(digit(3))
  .then('-')
  .then(digit(3))
  .then('-')
  .then(digit(4))

phone.test('123-456-7890')   // true
phone.test('+123-456-7890')  // true

Extract Date Parts

import { digit, capture } from 'zeroreg'

const datePattern = capture(digit(4), 'year')
  .then('-')
  .then(capture(digit(2), 'month'))
  .then('-')
  .then(capture(digit(2), 'day'))

const match = '2024-03-15'.match(datePattern.toRegex())
console.log(match.groups)
// { year: '2024', month: '03', day: '15' }

Password Validation

import { startOfLine, endOfLine, any, digit, letter, lookahead, charIn } from 'zeroreg'

const password = startOfLine()
  .then(lookahead(any().zeroOrMore().then(digit())))       // has digit
  .then(lookahead(any().zeroOrMore().then(letter())))      // has letter
  .then(lookahead(any().zeroOrMore().then(charIn('@$!%*?&')))) // has special
  .then(any().between(8, 32))
  .then(endOfLine())

password.test('MyP@ssw0rd')  // true
password.test('weakpass')    // false

Match Prices

import { literal, digit, lookbehind } from 'zeroreg'

// Match price amount after "$"
const price = lookbehind(literal('$'))
  .then(digit().oneOrMore())
  .then(literal('.').then(digit(2)).optional())

const matches = price.matchAll('Items: $10, $25.99, $100.00')
// ['10'], ['25.99'], ['100.00']

# TypeScript

Full TypeScript support out of the box. All functions are fully typed.

import { digit, capture, type Pattern } from 'zeroreg'

// Pattern type
const phone: Pattern = digit(3).then('-').then(digit(4))

// Named captures are type-safe
const date = capture(digit(4), 'year')
  .then('-')
  .then(capture(digit(2), 'month'))

const regex = date.toRegex()
const match = '2024-03'.match(regex)

if (match?.groups) {
  const { year, month } = match.groups  // TypeScript knows these exist
}