Skip to main content

Quickstart: build your first TS-Rex pattern

Install TS-Rex, chain builder methods to describe a pattern, call .compile(), and use .exec() to get a fully typed match result in minutes.

TS-Rex turns regex construction into a sequence of typed method calls. You chain builder methods to describe your pattern, call .compile() to get a typed execution wrapper, and then call .exec() on a string to get a result object whose properties TypeScript already knows. This page walks you through the full workflow from installation to your first match.

1. Install the package

Add TS-Rex to your project using your preferred package manager:

npm install @fajarnugraha37/ts-rex

TS-Rex requires TypeScript 5.0 or higher. See the installation page for all package managers and module format details.

2. Import the factory function

Import rx from the package. This is the only import you need to start building patterns.

import { rx } from '@fajarnugraha37/ts-rex';

3. Chain builder methods

Call rx() to create a fresh builder, then chain methods to describe your pattern. Each method returns a new immutable builder carrying the accumulated type state.

const pattern = rx()
.startOfInput()
.capture('firstName', rx().oneOrMore(rx().wordChar()))
.whitespace()
.capture('lastName', rx().oneOrMore(rx().wordChar()))
.endOfInput()
.compile();

The two .capture() calls tell TypeScript that the result will have firstName and lastName properties of type string.

4. Execute and access captures

Call .exec() on the compiled pattern. Check result.isMatch to narrow the type, then access your capture properties directly — no casting required.

const result = pattern.exec('John Doe');

if (result.isMatch) {
// Types are fully inferred from the captures defined above
console.log(result.firstName); // "John"
console.log(result.lastName); // "Doe"
console.log(result.match); // "John Doe" (the full match)
}

Global iteration

The .global() flag changes the return type of .exec() from a single result object to an IterableIterator. TS-Rex creates a fresh RegExp instance for every execution, so there are no lastIndex mutation bugs to worry about.

const pattern = rx()
.capture('num', rx().oneOrMore(rx().digit()))
.global()
.compile();

const results = pattern.exec('I have 3 apples and 42 bananas');

for (const result of results) {
console.log(result.num); // "3", then "42"
}

When .global() is set, .exec() always returns an IterableIterator — TypeScript reflects this in the return type automatically based on your builder chain.

Match indices

The .withIndices() flag (the ECMAScript d flag) adds an indices property to each match result. Each entry contains a [start, end] tuple for the full match and for every named capture group.

const pattern = rx()
.capture('val', rx().wordChar())
.withIndices()
.compile();

const result = pattern.exec('a');

if (result.isMatch) {
console.log(result.indices.match); // [0, 1]
console.log(result.indices.val); // [0, 1]
}

Alternation and union types

The .or() method matches either the pattern built so far or the pattern you pass in. At the type level, it resolves to a union — TypeScript enforces that exactly one branch matched, so capture properties from the other branch are typed as string | undefined.

const pattern = rx()
.capture('a', rx().literal('A'))
.or(rx().capture('b', rx().literal('B')))
.compile();

const result = pattern.exec('A');

if (result.isMatch) {
// TypeScript enforces that either 'a' is a string and 'b' is undefined, or vice versa.
console.log(result.a); // "A"
}