A declarative, human readable and friendly syntax sugar for regex.
npm install @regist/registRegist is a declarative, type-safe, and human-readable library for string validation and transformation.
* A declarative, human readable and friendly syntax sugar for regex
* To regex what ORMs are to SQL
* Turns cryptic regex into a nice tool anyone can write and maintain
* Makes you forget about complex slashes, brackets, and endless escaping
* Prevents regex bugs and technical debt in your codebase
* Expresses intent in English-like, readable syntax
* Intuitive and self-explanatory—even for beginners
* Zero regex knowledge required to read and write patterns
* Portable - same design can be implemented in any language that supports regex
* A regex engine
* A regex parser
* A schema validation libarary
* Only works with strings
``bash`
npm install @regist/regist
`typescript
import { assertThat } from "regist";
// Strong password (at least 8 chars, uppercase, lowercase, digit, special char)
assertThat("Aa1!aa11").isStrongPassword().try(); // true
assertThat("weakpass").isStrongPassword().try(); // false
// Email and URL validation
assertThat("foo@bar.com").isEmail().try(); // true
assertThat("not-an-email").isEmail().try(); // false
assertThat("https://foo.com").isUrl().try(); // true
assertThat("not a url").isUrl().try(); // false
// Phone validation (US)
assertThat("123-456-7890").isPhone("US").try(); // true
assertThat("notaphone").isPhone("US").try(); // false
`
`typescript
import { stringTransform } from "regist";
stringTransform(" myFileName.TXT ")
.trim()
.toLowerCase()
.toSnakeCase()
.try(); // "my_file_name.txt"
`
`typescript
stringTransform(" Hello World! ")
.trim()
.toLowerCase()
.assertThat()
.isExactly("hello world!")
.try(); // true
assertThat("foo")
.isExactly("foo")
.stringTransform()
.toUpperCase()
.try(); // "FOO"
`
`typescript
// Palindrome check (case/space insensitive)
stringTransform("A man a plan a canal Panama")
.sanitize({ removeSpaces: true })
.toLowerCase()
.assertThat()
.isPalindrome()
.try(); // true
// Anagram check
assertThat("listen").anagram("silent").try(); // true
assertThat("foo").anagram("bar").try(); // false
// Quick hashing with djb2 (great for grouping or hash-based problems)
const hash = stringTransform("anagram").toHash().try(); // e.g. "182755666"
// djb2 is a fast, well-known string hash: hash = 5381; for each char: hash = hash * 33 + code
// Example: group words by hash for anagram buckets
const words = ["listen", "silent", "enlist", "foo"];
const buckets = new Map
for (const word of words) {
const h = stringTransform(word).toHash().try();
buckets.set(h, [...(buckets.get(h) || []), word]);
}
console.log(buckets);
// Words with the same hash are likely anagrams (for small inputs, djb2 is often enough)
`
`typescript`
stringTransform("abc[foo]def").extractWhenBetween("[", "]").try(); // "foo"
stringTransform("abcdef").chunk(2, "-").try(); // "ab-cd-ef"
stringTransform("a,b,c").split(",").takeThatAt(1).try(); // "b"
stringTransform("a,b,c").split(",").join("-").try(); // "a-b-c"
`typescript
// Custom check (palindrome)
assertThat("racecar")
.customCheck(str => str === str.split("").reverse().join(""))
.try(); // true
// Handle errors gracefully
stringTransform("abc")
.extract(/\d+/)
.try((err, step, valueBefore) => {
// err.message contains "No match found"
// step === "extract"
// valueBefore === "abc"
}); // => null
`
Chains are reusable—you can build once, branch, and use in different contexts:
`typescript`
const snake = stringTransform("fooBar").toSnakeCase();
snake.try(); // "foo_bar"
snake.toUpperCase().try(); // "FOO_BAR"
Regist works well with other libraries (like zod):
`typescript
import { z } from "zod";
import { assertThat } from "regist";
const usernameSchema = z.string().refine(
val => assertThat(val).isAlphaNumeric().lengthIs({ min: 3, max: 20 }).try()
);
usernameSchema.parse("user123"); // passes
usernameSchema.parse("!@#"); // throws ZodError
`
- Readable: Your string logic should look like what you mean—no more regex puzzles.
- Declarative: Describe your intent, not mechanics.
- Assertion API (assertThat): Validate strings. Chain checks, get true/false or handle errors with .try(handler).stringTransform
- Transformation API (): Mutate or convert strings. Chain transformations, run with .try() or handle errors with .try(handler).
- Fully chainable and composable: Every method returns the API for further chaining.
- Interoperable: Freely switch between assertion and transform chains with .assertThat() or .stringTransform()..try()
- Reusable: Chains are lazy; build once, branch or reuse as many times as needed before calling .
- No work is done until .try() is called. Enables safe, reusable, and predictable logic.
- TypeScript-first for type safety and autocompletion.
- Fully tested, each method using vitest
- Core ideas and API are portable—adapt to any language.
- Both APIs support .try(handler).(error, stepName, valueAtFailure)
- If any step fails, your handler receives .
- If no handler is passed, errors are thrown.
| Method | Description | Regex Equivalent / Typical Regex |
| ---------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| .has(str) | Checks if value contains substring | str.includes("X") or /X/ |.doesNotHave(str)
| | Checks if value does NOT contain substring | !str.includes("X") or /X/.test(str) === false |.startsWith(str)
| | Checks if value starts with substring | /^X/ |.endsWith(str)
| | Checks if value ends with substring | /X$/ |.isExactly(str)
| | Checks for exact match | /^X$/ or str === "X" |.isNot()
| | Negates the next operation in the chain | !(...) |.isAlpha()
| | All characters are letters | /^[A-Za-z]+$/ |.isAlphaNumeric()
| | All characters are letters or digits | /^[A-Za-z0-9]+$/ |.hasAllUppercase()
| | All letters are uppercase | /^[A-Z]+$/ |.hasAllLowercase()
| | All letters are lowercase | /^[a-z]+$/ |.isUpperCase()
| | Value contains at least one uppercase letter | /[A-Z]/ |.isLowerCase()
| | Value contains at least one lowercase letter | /[a-z]/ |.isDigit()
| | All characters are digits | /^\d+$/ |.isNumeric()
| | String is number-convertible | /^-?\d+(\.\d+)?$/ |.isWhitespace()
| | All characters are whitespace | /^\s+$/ |.isEmail()
| | Valid email address | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |.isUrl()
| | Valid URL | /^(https?:\/\/)?[\w\-]+(\.[\w\-]+)+[/#?]?.*$/ (approx) |.isUrlSafe()
| | Only URL-safe characters | /^[A-Za-z0-9\-._~]+$/ |.isPhone(preset)
| | Valid phone number (with country preset e.g. 'US', 'UG') | Varies (e.g. /^\d{3}-\d{3}-\d{4}$/ for US) |.isNumberConvertible()
| | Can be safely converted to number | /^-?\d+(\.\d+)?$/ |.passesRegex(regex)
| | Custom regex match | Custom regex |.failsRegex(regex)
| | Custom regex non-match | Negated custom regex |.anyOf(...values)
| | Value is equal to any of provided values | /^(X | Y | Z)$/ |.lengthIs(n | {min,max})
| | Length equals or is in range | str.length === N or /^.{min,max}$/ |.isBetween(start, end)
| | Substring between two markers exists | /(?<=start).*?(?=end)/ |.hasNoEmoji()
| | Value contains no emoji | /[\p{Emoji}]/u (negated) |.anagram(str)
| | Is an anagram of provided string | none |.isPalindrome()
| | Is a palindrome | none |.hasUniqueCharacters()
| | All characters are unique | none |.whereValueAt(idx).is(val)
| | Character at index matches value | str[idx] === "X" |.wordCount()
| | Number of words | str.trim().split(/\s+/).length |.isBase64()
| | Valid base64 string | /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}== | [A-Za-z0-9+\/]{3}=)?$/ |.isStrongPassword()
| | Meets strong password criteria (min 8 chars, upper, lower, digit, special char) | /^(?=.[a-z])(?=.[A-Z])(?=.\d)(?=.[^A-Za-z0-9]).{8,}$/ |.customCheck(fn)
| | Use a custom predicate (must return boolean) | none |.stringTransform()
| | Switch to a transformation chain with the current value | none |.try(handler?)
| | Runs the chain, returns true/false, or calls handler on error | none |
| Method | Description | Regex Equivalent / Typical Regex |
| --------------------------------------- | ------------------------------------------------------------------- | -------------------------------- |
| .toUpperCase() | Uppercase the string | none |.toLowerCase()
| | Lowercase the string | none |.toPascalCase()
| | PascalCase | none |.toSnakeCase()
| | snake_case | none |.toCamelCase()
| | camelCase | none |.trim()
| | Trim whitespace | /^\s+ | \s+$/g |.split(separator).takeThatAt(idx)
| | Split and pick item at index | str.split(sep)[idx] |.split(separator).join(sep)
| | Split and join with separator | str.split(sep).join(sep2) |.getCharacterAt(idx)
| | Character at index | str[idx] |.getRandomFrom()
| | Random character | none |.replace(search, rep)
| | Replace first occurrence | str.replace(...) |.replaceAll(search, rep)
| | Replace all occurrences | str.replaceAll(...) or /g |.removeFirst(search)
| | Remove first occurrence | str.replace(...) |.removeAll(search)
| | Remove all occurrences | str.replaceAll(...) or /g |.add(prefix | {prefix, suffix})
| | Add prefix/suffix | none |.pad(len, char, where)
| | Pad to length (start/end/both) | none |.randomize()
| | Shuffle characters | none |.chunk(size, sep?)
| | Split into chunks of given length, joined by sep (default | ) | none |.breakToLines(lineLen)
| | Wrap into lines | none |.extract(pattern)
| | Extract match | .match(regex) |.extractInRange(start, end)
| | Extract substring by index | str.slice(start, end) |.extractWhenBetween(prefix, suffix)
| | Extract substring between two markers | /(?<=prefix).*?(?=suffix)/ |.toHex()
| | To hexadecimal | none |.toBinary()
| | To binary | none |.toURLSafe()
| | Percent-encode for URLs | encodeURIComponent(str) |.toHash()
| | Hash string using the djb2 algorithm (fast, non-cryptographic hash) | none |.customTransform(fn)
| | Custom string-to-string transform | none |.sanitize(opts)
| | Remove spaces, digits, or specials as specified | /\s+/g, /\d+/g, /[^\w]/g |.escapeString()
| / .unEscapeString() | Escape/unescape regex specials | none |.assertThat()
| | Switch to assertion API | none |.try(handler?)
| | Runs the transform chain, returns result or calls handler on error | none |
- Human-first: Code should read like you think. Explicit, natural, and clear—even when negating.
- Type-safe and robust: Full TypeScript, catching errors before runtime.
- Tested and trusted: Every feature covered by tests—production ready.
- Lazy and composable: Nothing runs until .try()`. Build, branch, and share chains as needed.
- Bidirectional chaining: Switch between assertion and transform logic without friction.
- Real-world ready: All the common string checks (email, phone, URL, emoji, etc.), plus leetcode-style utils, are built-in.
- Reusable and portable: Compose once, use everywhere, or implement Regist in any language that speaks regex.
- Zero dependencies: No bloat, just TypeScript/JavaScript and RegExp.
Pull requests and suggestions are welcome! Please open issues for bugs or feature requests.
> Ps: This is still a work in progress, so expect changes and improvements over time and yeah am not a regex expert, wish I was, so if you find any bugs or have suggestions, please let me know at end of the day this project is meant to make this approachable for everyone.
MIT License