Str — String Utilities
2.20 kB minified + gzipped
Strings are the primary medium for untrusted data entering an application. Whether you are parsing HTTP headers, processing CSV uploads, or sanitizing user-submitted form fields, raw text requires constant validation and formatting.
In standard JavaScript, the String prototype offers plenty of methods, but using them in data
pipelines introduces three recurring problems:
- Prototype methods are data-first: Because
.trim(),.replace(), and.slice()are called on the string instance, mixing them with standalone validators or array combinators insidepipeforces you to wrap every step in an inline arrow function (s => s.trim()). - Unsafe numeric parsing: Built-in functions like
Number.parseInt()andNumber.parseFloat()returnNaNwhen given malformed text, which silently propagates through calculations as a validnumbertype. - Stateful regular expressions and UTF-16 length traps: Global
/gregular expressions mutate their internallastIndexcursor between calls, causing intermittent match failures, while.lengthcounts UTF-16 code units rather than visible characters, miscounting emojis and international text.
The moment you need to insert a custom validation step or convert types mid-chain, prototype
chaining breaks down into temporary variables. Str solves this by providing pure, curried,
data-last string operations alongside safe Maybe-returning parsers and stateless regex
combinators.
flowchart TB
A["' alex , , bob '"] -->|"Str.trim"| B["'alex , , bob'"]
B -->|"Str.split(',')"| C["['alex ', ' ', ' bob']"]
C -->|"Arr.map(Str.trim)"| D["['alex', '', 'bob']"]
D -->|"Arr.filter(Str.is.nonEmpty)"| E["['alex', 'bob']"]
Checking Emptiness and Blank Strings
Section titled “Checking Emptiness and Blank Strings”When validating form inputs or filtering parsed tokens, you often need to distinguish between a
string with zero characters and a string that contains only whitespace. Str.is provides three
predicates:
Str.is.empty: Returnstruewhentext.length === 0.Str.is.nonEmpty: Type guard that returnstruewhentext.length > 0, narrowing the type toStr.NonEmpty.Str.is.blank: Returnstruewhen the string is empty or consists exclusively of whitespace characters (" \t\n").
When a function requires a compile-time guarantee that a string is not empty, use the branded
Str.NonEmpty type. You can refine any string into Maybe<Str.NonEmpty> using
Str.NonEmpty.from.string, or narrow it in place with Str.is.nonEmpty:
Transforming Case and Cleaning Input
Section titled “Transforming Case and Cleaning Input”Basic formatting operations are curried and data-last, allowing you to pass them directly to pipe
or Arr.map without inline lambdas. Alongside Str.toUpperCase and Str.toLowerCase,
Str.capitalize uppercases only the first character while Str.uncapitalize lowercases only the
first character (useful when converting PascalCase identifiers to camelCase):
Slicing, Padding, and Truncating
Section titled “Slicing, Padding, and Truncating”To extract substrings, align fixed-width columns, or shorten long descriptions for UI previews,
Str provides data-last wrappers for slice, padStart, padEnd, and truncate:
When displaying previews or summaries in notifications and tables, Str.truncate caps a string at a
maximum length (including the suffix) and appends "..." or a custom suffix only when the text
actually exceeds the limit:
Measuring Length: size and graphemeSize
Section titled “Measuring Length: size and graphemeSize”JavaScript’s native String.prototype.length counts UTF-16 code units rather than human-perceived
characters. Multi-byte characters such as emojis, accented letters composed of combining marks, or
zero-width joiner sequences report unexpected lengths ("👋".length === 2 and
"👨👩👧👦".length === 11).
Str.size returns the fast UTF-16 code-unit length for byte-level checks, while Str.graphemeSize
uses Intl.Segmenter to count actual user-perceived characters (grapheme clusters):
Splitting and Segmenting Text
Section titled “Splitting and Segmenting Text”Standard JavaScript .split() handles mixed line endings and repeated spaces awkwardly. Alongside
curried Str.split, Str.lines normalizes \n, \r\n, and \r line breaks across operating
systems, and Str.words splits text on any whitespace boundary while automatically discarding empty
tokens:
Replacing Text
Section titled “Replacing Text”Str.replace and Str.replaceAll accept the search pattern (string or RegExp) and replacement
string first, leaving the target text for last:
Substring Predicates: startsWith, endsWith, and includes
Section titled “Substring Predicates: startsWith, endsWith, and includes”String matching functions in Str are curried predicates that slot directly into Arr.filter or
Maybe.filter:
Pattern Matching with Regular Expressions
Section titled “Pattern Matching with Regular Expressions”Native RegExp instances configured with global (/g) or sticky (/y) flags mutate their internal
lastIndex property on every .test() or .exec() call, causing the exact same regex check to
alternate between true and false across consecutive calls.
Str.match and Str.test reset pattern.lastIndex = 0 before every execution, making regular
expression checks completely stateless and repeatable.
Str.match returns Some<RegExpMatchArray> when the pattern matches, or None when it does not:
Str.test returns a boolean and works directly as a reusable validation predicate:
Safe Numeric Parsing
Section titled “Safe Numeric Parsing”To parse numeric text without letting NaN slip into calculations, Str.parse.int and
Str.parse.float return Maybe<number>, returning None whenever the string is empty or cannot be
parsed:
Pipeline Composition
Section titled “Pipeline Composition”By combining Str and Arr, you can express multi-stage text normalization and extraction
workflows as a single linear pipeline:
Problems it solves
Section titled “Problems it solves”- Form input sanitization in data pipelines: When cleaning user input (such as trimming
whitespace, normalizing casing, padding identifiers, or truncating descriptions), native string
methods require writing manual arrow wrappers inside
pipe.Strprovides curried, data-last combinators (Str.trim,Str.toLowerCase,Str.capitalize,Str.uncapitalize,Str.slice,Str.padStart,Str.padEnd,Str.truncate) that chain directly. - Accurate character and grapheme counting (
Str.size,Str.graphemeSize): In internationalized applications or user interfaces with character counters, native.lengthmiscounts surrogate pairs and compound emojis.Str.graphemeSizecounts actual user-perceived glyphs viaIntl.Segmenter, whileStr.sizeprovides a uniform data-last UTF-16 code-unit accessor. - Stateless regular expression matching without pointer bugs: Native
RegExpflags (/g,/y) maintain internallastIndexpointer offsets that cause successive checks to fail intermittently.Str.matchandStr.testreset state to index zero on every execution, guaranteeing idempotent, side-effect-free pattern matching. - Safe numeric string conversion: Parsing numbers from HTTP parameters or form text with
Number()orparseInt()yieldsNaNon invalid input without static compiler warnings.Str.parse.intandStr.parse.floatreturnMaybe<number>, ensuring non-numeric inputs are handled safely before doing math. - Compile-time non-empty string branding (
Str.NonEmpty,Str.is.nonEmpty): Validating at API boundaries that required text fields (like API keys or resource IDs) are not empty and preserving that guarantee in the type system viaStr.NonEmpty.from.stringandStr.is.nonEmpty. - Named string predicates in array pipelines: Filtering arrays of strings with named predicates
(
Str.is.empty,Str.is.nonEmpty,Str.is.blank,Str.startsWith,Str.endsWith,Str.includes) without writing inline lambda wrappers. - Cross-platform multi-line and token text parsing: Splitting text into lines or words often
breaks on mixed CRLF/LF line endings or multiple consecutive whitespace characters.
Str.linesandStr.wordshandle cross-platform line breaks and variable spacing automatically.