Str
String utilities for text manipulation and analysis.
Provides comprehensive string operations including case conversion, splitting, matching, replacement, templating, and character utilities. Features type-safe APIs with strong inference for string literals and patterns.
Import
import { Str } from '@wollybeard/kit'import * as Str from '@wollybeard/kit/str'Namespaces
| Namespace | Description |
|---|---|
Char | Uppercase letter. |
Code | Code generation and documentation utilities. |
Provides tools for generating markdown, TSDoc/JSDoc, and TypeScript code. Includes safe JSDoc generation with escaping, builder API, and structured tag helpers. | | Text | Multi-line text formatting and layout utilities.
Provides functions specifically for working with multi-line strings treated as text content: - Line operations: Split into lines, join lines, map transformations per line - Indentation: Add/remove indentation, strip common leading whitespace - Alignment: Pad text, span to width, fit to exact width - Block formatting: Format blocks with prefixes, styled borders
Use Text for: Operations that treat strings as multi-line content with visual layout (indentation, padding for tables, line-by-line transformations).
Use root Str for: Primitive string operations (split, join, replace, match, trim) that work on strings as atomic values. | | Visual | Visual-aware string utilities that handle ANSI escape codes and grapheme clusters.
These functions measure and manipulate strings based on their visual appearance, not raw character count. Useful for terminal output, tables, and formatted text. |
Builder
[C] defaultRender
;((value: string[]) => string)Default render function for string builders. Joins lines with newline characters.
[I] Builder
interface Builder {
/**
* Add lines to the builder.
* @param linesInput - Lines to add (null values are filtered out)
* @returns The builder instance for chaining
*/
(...linesInput: LinesInput): Builder
/**
* Add content using template literal syntax.
* @param strings - Template string array
* @param values - Interpolated values
* @returns The builder instance for chaining
*/
(strings: TemplateStringsArray, ...values: string[]): Builder
/**
* The internal state containing accumulated lines.
*/
state: State
/**
* Render the accumulated lines into a single string.
* @returns The rendered string
*/
render: () => string
/**
* Alias for render() to support string coercion.
* @returns The rendered string
*/
toString(): string
}String builder interface for constructing multi-line strings. Supports both function call syntax and template literal syntax.
[F] Builder
(options?: { join?: string; } | undefined): BuilderReturns: A new builder instance
Create a new string builder for constructing multi-line strings.
Examples:
const b = Str.Builder()
b('Line 1')
b('Line 2', 'Line 3')
b`Template line`
console.log(b.render()) // "Line 1\nLine 2\nLine 3\nTemplate line"[T] LinesInput
type LinesInput = (Line | null)[]Input type for lines
- allows null values which are filtered out.
[T] Lines
type Lines = Line[]Array of line strings.
[T] Line
type Line = stringA single line of text.
[I] State
interface State {
/**
* Accumulated lines.
*/
lines: Lines
}Internal state of the string builder.
Constants
[C] Empty
''Empty string constant.
Examples:
const result = someCondition ? 'hello' : EmptyFormatting
[F] table
(input: { data: Record<string, string>; separator?: string | false | undefined; separatorAlignment?: boolean; }): stringParameters:
input- Configuration object
Returns: Formatted table string with aligned columns
Format a key-value object as an aligned table string.
Examples:
Str.table({
data: { name: 'John', age: '25', city: 'NYC' },
})
// Returns:
// name → John
// age → 25
// city → NYC
Str.table({
data: { foo: 'bar', hello: 'world' },
separator: ' = ',
separatorAlignment: false,
})
// Returns:
// foo = bar
// hello = worldPattern Matching
[F] pattern
<matches extends Matches>(pattern: RegExp): Pattern<matches>Parameters:
pattern- The regular expression pattern
Returns: A typed pattern that preserves capture group information
Create a typed pattern from a regular expression. Enables type-safe capture groups when used with match.
Examples:
const p = pattern<{ groups: ['name', 'age'] }>(/(?<name>\w+) is (?<age>\d+)/)
const result = match('John is 25', p)
if (Option.isSome(result)) {
console.log(result.value.groups.name) // 'John' (typed)
console.log(result.value.groups.age) // '25' (typed)
}[F] match
<matches extends Matches>(string: string, pattern: RegExp | Pattern<matches>): Option<RegExpMatchResult<matches>>Parameters:
string- The string to match againstpattern- Regular expression or typed pattern
Returns: Option of match result with typed capture groups, or None if no match
Match a string against a pattern with type-safe results.
Examples:
const result = Str.match('hello world', /hello (\w+)/)
if (Option.isSome(result)) {
console.log(result.value[0]) // 'hello world'
console.log(result.value[1]) // 'world'
}Predicates
[F] isMatch
(value: string, pattern: PatternInput): booleanParameters:
value- The string to testpattern- String for exact match or RegExp for pattern match
Returns: True if the value matches the pattern
Check if a string matches a pattern.
Examples:
Str.isMatch('hello', 'hello') // true
Str.isMatch('hello', /^h.*o$/) // true
Str.isMatch('world', 'hello') // false[C] isMatchOn
;((value: string) => (pattern: PatternInput) => boolean)Curried version of isMatch with value first.
Examples:
const isHello = Str.isMatchOn('hello')
isHello('hello') // true
isHello(/^h.*o$/) // true[C] isMatchWith
;((pattern: PatternInput) => (value: string) => boolean)Curried version of isMatch with pattern first.
Examples:
const matchesHello = Str.isMatchWith('hello')
matchesHello('hello') // true
matchesHello('world') // false[F] isntMatch
(pattern: PatternInput): (value: string) => booleanParameters:
pattern- String for exact match or RegExp for pattern match
Returns: Function that takes a value and returns true if it doesn't match
Check if a string does not match a pattern.
Examples:
const notHello = Str.isntMatch('hello')
notHello('world') // true
notHello('hello') // false[C] isntMatchOn
;((pattern: PatternInput) => (value: string) => boolean)Curried version of isntMatch with value first.
[C] isntMatchWith
;((value: string) => (pattern: PatternInput) => boolean)Curried version of isntMatch with pattern first.
[F] isMatchAny
(value: string, patterns: PatternsInput): booleanParameters:
value- The string to testpatterns- Array of strings or RegExp patterns (or a single pattern)
Returns: True if the value matches any pattern
Check if a string matches any of the provided patterns.
Examples:
Str.isMatchAny('hello', ['hello', 'world']) // true
Str.isMatchAny('hello', [/^h/, /o$/]) // true
Str.isMatchAny('foo', ['hello', 'world']) // false[C] isMatchAnyOn
;((value: string) => (patterns: PatternsInput) => boolean)Curried version of isMatchAny with value first.
[C] isMatchAnyWith
;((patterns: PatternsInput) => (value: string) => boolean)Curried version of isMatchAny with patterns first.
Examples:
const matchesGreeting = Str.isMatchAnyWith(['hello', 'hi', /^hey/])
matchesGreeting('hello') // true
matchesGreeting('hey there') // true
matchesGreeting('goodbye') // false[F] isNotMatchAny
(patternOrPatterns: PatternsInput): (value: string) => booleanParameters:
patternOrPatterns- Array of strings or RegExp patterns (or a single pattern)
Returns: Function that takes a value and returns true if it doesn't match any pattern
Check if a string does not match any of the provided patterns.
Examples:
const notGreeting = Str.isNotMatchAny(['hello', 'hi'])
notGreeting('goodbye') // true
notGreeting('hello') // false[C] isNotMatchAnyOn
;((patternOrPatterns: PatternsInput) => (value: string) => boolean)Curried version of isNotMatchAny with value first.
[C] isNotMatchAnyWith
;((value: string) => (patternOrPatterns: PatternsInput) => boolean)Curried version of isNotMatchAny with patterns first.
Runtime Utilities $S - The string to measure
[F] length
<$S extends string>(s: $S): Length<$S, boolean>Get the length of a string.
Runtime function with type-level literal inference. For literal strings, the return type is the exact length. For non-literal strings, returns number.
Examples:
const len1 = Str.length('hello') // Type: 5, Runtime: 5
const len2 = Str.length('') // Type: 0, Runtime: 0
declare const s: string
const len3 = Str.length(s) // Type: number, Runtime: s.lengthTemplate
[F] interpolate
(template: string): (args: TemplateArgs) => stringParameters:
template- Template string containing $variable placeholders
Returns: Function that takes args object and returns interpolated string
Interpolate variables into a template string using $variable syntax.
Examples:
const greeting = Str.interpolate('Hello ${name}, you are ${age} years old')
greeting({ name: 'John', age: 25 }) // 'Hello John, you are 25 years old'
const template = Str.interpolate('${greeting} ${name}!')
template({ greeting: 'Hi', name: 'Alice' }) // 'Hi Alice!'[C] templateVariablePattern
RegExpRegular expression pattern to match template variables in $variable format. Captures the variable name inside the braces.
[T] TemplateArgs
type TemplateArgs = Record<string, Json.Value>Arguments object for template interpolation. Maps variable names to their JSON-serializable values.
Text Formatting
[Class] Box
class {
// Properties
paddingHooks: Partial<Record<"mainStart" | "mainEnd" | "crossStart" | "crossEnd", ((ctx: any) => number | ((v: number) => number))[]>>
marginHooks: Partial<Record<"mainStart" | "mainEnd" | "crossStart" | "crossEnd", ((ctx: any) => number | ((v: number) => number))[]>>
borderEdgeHooks: Partial<Record<"left" | "right" | "top" | "bottom", ((ctx: any) => string | ((v: string) => string))[]>>
borderCornerHooks: Partial<Record<"topLeft" | "topRight" | "bottomRight" | "bottomLeft", ((ctx: any) => string | ((v: string) => string))[]>>
borderEdgeStyles: Partial<Record<"left" | "right" | "top" | "bottom", Style>>
borderCornerStyles: Partial<Record<"topLeft" | "topRight" | "bottomRight" | "bottomLeft", Style>>
static String: transformOrFail<typeof Box, typeof String, never>
// Methods
toString(): string
content$(content: string | (string | Box)[]): this
pad$(padding: PaddingInput): this
margin$(margin: MarginInput): this
border$(border: BorderInput): this
span$(span: SpanInput): this
spanRange$(spanRange: { readonly main?: { readonly min?: number | undefined; readonly max?: number | undefined; } | undefined; readonly cross?: { readonly min?: number | undefined; readonly max?: number | undefined; } | undefined; }): this
gap$(gap: GapInput): this
static content(box: Box, content: string | (string | Box)[]): Box
static pad(box: Box, padding: PaddingInput): Box
static margin(box: Box, margin: MarginInput): Box
static border(box: Box, border: BorderInput): Box
static span(box: Box, span: SpanInput): Box
static spanRange(box: Box, spanRange: { readonly main?: { readonly min?: number | undefined; readonly max?: number | undefined; } | undefined; readonly cross?: { readonly min?: number | undefined; readonly max?: number | undefined; } | undefined; }): Box
static gap(box: Box, gap: GapInput): Box
static encode(box: Box): string
}Properties:
String- Schema for encoding Box to string representation.
This is a one-way transformation - boxes can be encoded to strings, but cannot be decoded from strings.
Box structure with content and optional styling.
[C] OrientationSchema
Literal<['vertical', 'horizontal']>Orientation determines the flow direction of the box.
vertical: Content flows top-to-bottom (main axis = vertical)horizontal: Content flows left-to-right (main axis = horizontal)
[T] Orientation
type Orientation = typeof OrientationSchema.TypeOrientation type.
[C] PaddingSchema
Struct<
{
mainStart: optional<typeof Number>
mainEnd: optional<typeof Number>
crossStart: optional<typeof Number>
crossEnd: optional<typeof Number>
}
>Padding configuration using logical properties.
Logical properties adapt to orientation:
mainStart/mainEnd: Along the flow directioncrossStart/crossEnd: Perpendicular to flow
[T] Padding
type Padding = typeof PaddingSchema.TypePadding configuration type.
[U] PaddingInput
type PaddingInput = AxisHand.Input | WithHooks<Padding, 'padding'>Padding input accepting AxisHand notation and hook functions.
Supports AxisHand patterns:
- Single value:
2→ all sides - Axis shorthands:
[2, 4]→ [main, cross] - Binary axis:
[[1, 2], [3, 4]]→ [[mainStart, mainEnd], [crossStart, crossEnd]] - Per-axis arrays:
[[1, 2], 4]→ asymmetric main, symmetric cross - Object:
{ main: [1, 2], cross: 4 } - With hooks:
{ main: { start: (ctx) => 2 } }
[C] MarginSchema
Struct<
{
mainStart: optional<typeof Number>
mainEnd: optional<typeof Number>
crossStart: optional<typeof Number>
crossEnd: optional<typeof Number>
}
>Margin configuration using logical properties.
Logical properties adapt to orientation (same as Padding).
[T] Margin
type Margin = typeof MarginSchema.TypeMargin configuration type.
[U] MarginInput
type MarginInput = AxisHand.Input | WithHooks<Margin, 'margin'>Margin input accepting AxisHand notation and hook functions.
Supports AxisHand patterns (same as PaddingInput).
[U] SpanValue
type SpanValue = number | bigintSpan value type
size in characters or percentage of parent.
number(1): Absolute size in charactersbigint: Percentage of parent span (e.g.,50n= 50%)
[C] SpanSchema
Struct<
{
main: optional<Union<[typeof Number, typeof BigIntFromSelf]>>
cross: optional<Union<[typeof Number, typeof BigIntFromSelf]>>
}
>Span configuration using logical properties.
Defines exact/desired size along each axis:
main: Size along flow direction (mainSpan)cross: Size perpendicular to flow (crossSpan)
Percentage values (bigint) are resolved relative to parent's available span.
[T] Span
type Span = typeof SpanSchema.TypeSpan configuration type.
[T] SpanInput
type SpanInput = AxisHand.Input<SpanValue>Span input accepting AxisHand notation.
Supports AxisHand patterns with SpanValue (number | bigint):
- Single value:
80→ main and cross both 80 chars - Single percentage:
50n→ main and cross both 50% of parent - Axis shorthands:
[50n, 80]→ main 50%, cross 80 chars - Binary axis:
[[40, 50n], [80, 100]]→ different start/end (unusual for span) - Object:
{ main: 50n, cross: 80 }
[C] SpanRangeSchema
Struct<
{
main: optional<
Struct<{ min: optional<typeof Number>; max: optional<typeof Number> }>
>
cross: optional<
Struct<{ min: optional<typeof Number>; max: optional<typeof Number> }>
>
}
>Span range constraints (min/max) using logical properties.
[T] SpanRange
type SpanRange = typeof SpanRangeSchema.TypeSpan range configuration type.
[C] GapSchema
Struct<{ main: optional<typeof Number>; cross: optional<typeof Number> }>Gap configuration using logical properties.
Defines space between array items (container property):
- Vertical orientation: main=newlines between items, cross=spaces between items
- Horizontal orientation: main=spaces between items, cross=newlines between items
[T] Gap
type Gap = typeof GapSchema.TypeGap configuration type.
[U] GapInput
type GapInput = number | GapGap input accepting number or object with logical properties.
number: Same gap on both axes{ main?: number, cross?: number }: Per-axis gaps
[C] BorderStyleSchema
Literal<['single', 'double', 'rounded', 'bold', 'ascii']>Border style presets.
[T] BorderStyle
type BorderStyle = typeof BorderStyleSchema.TypeBorder style preset type.
[C] BorderEdgesSchema
Struct<
{
top: optional<typeof String>
right: optional<typeof String>
bottom: optional<typeof String>
left: optional<typeof String>
}
>Border edge characters (physical coordinates).
[T] BorderEdges
type BorderEdges = typeof BorderEdgesSchema.TypeBorder edge configuration type.
[C] BorderCornersSchema
Struct<
{
topLeft: optional<typeof String>
topRight: optional<typeof String>
bottomRight: optional<typeof String>
bottomLeft: optional<typeof String>
}
>Border corner characters (physical coordinates).
[T] BorderCorners
type BorderCorners = typeof BorderCornersSchema.TypeBorder corner configuration type.
[U] BorderEdgesInput
type BorderEdgesInput =
| Clockhand.Value<string | CharStyle>
| WithHooks<BorderEdges, 'border.edges'>
| {
[K in keyof BorderEdges]?:
| string
| CharStyle
| WithHook<
string | undefined,
StyleCategoryMap[`border.edges.${K & string}`]
>
}Border edge input supporting Clockhand notation, CharStyle, and hook functions.
Supports Clockhand patterns:
- Single value:
'─'→ all edges - Single styled:
{ char: '─', color: { foreground: 'blue' } }→ all edges - Array:
['─', '│', '─', '│']→ [top, right, bottom, left] - Object:
{ top: '─', left: '│' } - Object with CharStyle:
{ top: { char: '─', color: { foreground: 'red' } } } - With hooks:
{ top: (ctx) => '─' }
[U] BorderCornersInput
type BorderCornersInput =
| Clockhand.Value<string | CharStyle>
| WithHooks<BorderCorners, 'border.corners'>
| {
[K in keyof BorderCorners]?:
| string
| CharStyle
| WithHook<
string | undefined,
StyleCategoryMap[`border.corners.${K & string}`]
>
}Border corner input supporting Clockhand notation, CharStyle, and hook functions.
Supports Clockhand patterns:
- Single value:
'+'→ all corners - Single styled:
{ char: '+', color: { foreground: 'yellow' }, bold: true }→ all corners - Array:
['┌', '┐', '┘', '└']→ [topLeft, topRight, bottomRight, bottomLeft] (clockwise) - Object:
{ topLeft: '┌', topRight: '┐' } - Object with CharStyle:
{ topLeft: { char: '┌', color: { foreground: 'red' }, bold: true } } - With hooks:
{ topLeft: (ctx) => '┌' }
[T] BorderCharsInput
type BorderCharsInput = {
edges?: BorderEdgesInput
corners?: BorderCornersInput
}Border character configuration input with nested edges/corners.
[C] BorderSchema
Struct<
{
style: optional<Literal<['single', 'double', 'rounded', 'bold', 'ascii']>>
edges: optional<
Struct<
{
top: optional<typeof String>
right: optional<typeof String>
bottom: optional<typeof String>
left: optional<typeof String>
}
>
>
corners: optional<
Struct<
{
topLeft: optional<typeof String>
topRight: optional<typeof String>
bottomRight: optional<typeof String>
bottomLeft: optional<typeof String>
}
>
>
}
>Border configuration.
Can specify a preset style, custom edges, custom corners, or a combination. Resolution order: style → edges override → corners override.
[T] Border
type Border = typeof BorderSchema.TypeBorder configuration type.
[T] BorderInput
type BorderInput = {
style?: BorderStyle
edges?: BorderEdgesInput
corners?: BorderCornersInput
}Border configuration input with hook support.
Supports:
style: Preset border style (provides edges and corners)edges: Edge characters (with Clockhand support)corners: Corner characters (with Clockhand support)
Resolution order: style → edges/corners override
[U] BoxContent
type BoxContent = string | StyledText | readonly (string | StyledText | Box)[]Content type for Box
- can be a string, styled text, or array of these and boxes.
Supports:
- Plain strings:
'Hello' - Styled text:
{ text: 'Hello', color: { foreground: 'red' }, bold: true } - Arrays:
['Header', { text: 'Body', color: { foreground: 'green' } }, Box.make(...)]
Traits
[C] Eq
Eq<string>Eq trait implementation for strings.
Provides string equality comparison using strict equality (===). String comparison is case-sensitive and considers all Unicode characters.
Examples:
import { Str } from '@wollybeard/kit'
Str.Eq.is('hello', 'hello') // true
Str.Eq.is('hello', 'Hello') // false (case-sensitive)
Str.Eq.is('', '') // true (empty strings)[C] Type
Type<string>Type trait implementation for strings.
Provides type guard for checking if a value is a string.
Examples:
import { Str } from '@wollybeard/kit'
Str.Type.is('hello') // true
Str.Type.is(123) // false
Str.Type.is(null) // falseTransformation
[F] titlizeSlug
(str: string): stringParameters:
str- The slug string to convert
Returns: The title-cased string
Convert a URL slug to title case. Replaces URL path separators with spaces and converts to title case.
Examples:
Str.titlizeSlug('foo/bar/baz') // 'Foo Bar Baz'
Str.titlizeSlug('the/quick/brown/fox') // 'The Quick Brown Fox'
Str.titlizeSlug('hello-world') // 'Hello-World' (hyphens are preserved)[F] ensureEnd
(string: string, ending: string): stringParameters:
string- The string to checkending- The ending to ensure
Returns: The string with the ending ensured
Ensure a string ends with a specific ending, adding it if not present.
[F] trim
(value: string): stringParameters:
value- The string to trim
Returns: The trimmed string
DEPRECATED
Use String.trim from Effect instead
Remove whitespace from both ends of a string.
Examples:
Str.trim(' hello ') // 'hello'
Str.trim('\n\thello\n\t') // 'hello'[F] replaceLeading
(replacement: string, matcher: string, value: string): stringParameters:
replacement- The string to replace the matcher withmatcher- The string to match at the beginningvalue- The string to operate on
Returns: The string with leading matcher replaced
Replace the leading occurrence of a matcher string with a replacement.
Examples:
Str.replaceLeading('$', '//', '// comment') // '$ comment'
Str.replaceLeading('', 'www.', 'www.example.com') // 'example.com'[F] replaceLeadingWith
(replacement: string): (matcher: string) => (value: string) => stringParameters:
replacement- The string to replace the matcher with
Returns: Function that takes matcher, then value
Curried version of replaceLeading with replacement first.
[F] replaceLeadingOn
(value: string): (replacement: string) => (matcher: string) => stringParameters:
value- The string to operate on
Returns: Function that takes replacement, then matcher
Curried version of replaceLeading with value first.
[C] stripLeading
;((matcher: string) => (value: string) => string)Remove the leading occurrence of a matcher string. Alias for replaceLeadingWith('').
Examples:
const removePrefix = Str.stripLeading('//')
removePrefix('// comment') // ' comment'[F] replace
(replacement: string, matcher: PatternsInput, value: string): stringParameters:
replacement- The string to replace matches withmatcher- String or RegExp pattern(s) to matchvalue- The string to operate on
Returns: The string with all matches replaced
DEPRECATED
Use String.replace or String.replaceAll from Effect instead
Replace all occurrences of patterns with a replacement string.
Examples:
Str.replace('_', ' ', 'hello world') // 'hello_world'
Str.replace('X', /[aeiou]/g, 'hello') // 'hXllX'
Str.replace('-', [' ', '_'], 'hello world_test') // 'hello-world-test'[F] replaceWith
(replacement: string): (matcher: PatternsInput) => (value: string) => stringParameters:
replacement- The string to replace matches with
Returns: Function that takes matcher, then value
Curried version of replace with replacement first.
[F] replaceOn
(value: string): (replacement: string) => (matcher: PatternsInput) => stringParameters:
value- The string to operate on
Returns: Function that takes replacement, then matcher
Curried version of replace with value first.
[F] append
(value1: string, value2: string): stringParameters:
value1- The base stringvalue2- The string to append
Returns: The concatenated string
DEPRECATED
Use String.concat from Effect instead
Append a string to another string.
Examples:
Str.append('hello', ' world') // 'hello world'
Str.append('foo', 'bar') // 'foobar'[C] appendOn
;((value1: string) => (value2: string) => string)Curried version of append with value1 first.
[C] appendWith
;((value2: string) => (value1: string) => string)Curried version of append with value2 first.
Examples:
const addWorld = Str.appendWith(' world')
addWorld('hello') // 'hello world'[F] prepend
(value1: string, value2: string): stringParameters:
value1- The string to prependvalue2- The base string
Returns: The concatenated string with value1 first
DEPRECATED
Use String.concat from Effect instead (with arguments swapped)
Prepend a string to another string.
Examples:
Str.prepend('hello ', 'world') // 'hello world'
Str.prepend('pre', 'fix') // 'prefix'[C] prependOn
;((value1: string) => (value2: string) => string)Curried version of prepend with value1 first.
[C] prependWith
;((value2: string) => (value1: string) => string)Curried version of prepend with value2 first.
Examples:
const toWorld = Str.prependWith('world')
toWorld('hello ') // 'hello world'[F] repeat
(value: string, count: number): stringParameters:
value- The string to repeatcount- The number of times to repeat
Returns: The repeated string
DEPRECATED
Use String.repeat from Effect instead
Repeat a string a specified number of times.
Examples:
Str.repeat('a', 3) // 'aaa'
Str.repeat('hello', 2) // 'hellohello'
Str.repeat('-', 10) // '----------'[C] repeatOn
;((value: string) => (count: number) => string)Curried version of repeat with value first.
[C] repeatWith
;((count: number) => (value: string) => string)Curried version of repeat with count first.
Examples:
const triple = Str.repeatWith(3)
triple('ha') // 'hahaha'[F] removeSurrounding
(str: string, target: string): stringParameters:
str- The string to processtarget- The character to remove from both ends
Returns: The string with surrounding target characters removed
Remove all occurrences of a target character from the beginning and end of a string.
Examples:
Str.removeSurrounding(' hello ', ' ') // 'hello'
Str.removeSurrounding('***test***', '*') // 'test'
Str.removeSurrounding('aaa', 'a') // ''[C] removeSurroundingOn
;((str: string) => (target: string) => string)Curried version of removeSurrounding with str first.
[C] removeSurroundingWith
;((target: string) => (str: string) => string)Curried version of removeSurrounding with target first.
[F] truncate
(str: string, maxLength?: number = 80): stringParameters:
str- The string to truncatemaxLength- Maximum length of the result (default: 80)
Returns: The truncated string with ellipsis if needed
Truncate a string to a maximum length, adding ellipsis if truncated.
Examples:
Str.truncate('hello world', 8) // 'hello...'
Str.truncate('short', 10) // 'short'
Str.truncate('very long text that needs truncating') // 'very long text that needs truncating...' (if > 80 chars)[C] truncateOn
;((str: string) => (maxLength?: number | undefined) => string)Curried version of truncate with str first.
[C] truncateWith
;((maxLength?: number | undefined) => (str: string) => string)Curried version of truncate with maxLength first.
Examples:
const truncate10 = Str.truncateWith(10)
truncate10('hello world') // 'hello w...'[C] strip
;((matcher: PatternsInput) => (value: string) => string)Remove all occurrences of patterns from a string. Alias for replaceWith('').
Examples:
const removeVowels = Str.strip(/[aeiou]/g)
removeVowels('hello world') // 'hll wrld'[C] removeSurroundingSpaceRegular
;((str: string) => string)Remove regular spaces from the beginning and end of a string. Pre-configured removeSurroundingWith for regular spaces.
[C] removeSurroundingSpaceNoBreak
;((str: string) => string)Remove non-breaking spaces from the beginning and end of a string. Pre-configured removeSurroundingWith for non-breaking spaces.
[F] split
(value: string, separator: string): string[]Parameters:
value- The string to splitseparator- The separator to split on
Returns: Array of substrings
DEPRECATED
Use String.split from Effect instead
Split a string into an array of substrings using a separator.
Examples:
Str.split('a,b,c', ',') // ['a', 'b', 'c']
Str.split('hello world', ' ') // ['hello', 'world']
Str.split('', ',') // [][C] splitOn
(value: string) => (separator: string) => string[]Curried version of split with value first.
[C] splitWith
(separator: string) => (value: string) => string[]Curried version of split with separator first.
Examples:
const splitByComma = Str.splitWith(',')
splitByComma('a,b,c') // ['a', 'b', 'c'][F] join
(value: string[], separator: string): stringParameters:
value- Array of strings to joinseparator- The separator to place between strings
Returns: The joined string
DEPRECATED
Use Array.join from Effect instead
Join an array of strings into a single string with a separator.
Examples:
Str.join(['a', 'b', 'c'], ',') // 'a,b,c'
Str.join(['hello', 'world'], ' ') // 'hello world'
Str.join([], ',') // ''[C] joinOn
;((value: string[]) => (separator: string) => string)Curried version of join with value first.
[C] joinWith
;((separator: string) => (value: string[]) => string)Curried version of join with separator first.
Examples:
const joinWithComma = Str.joinWith(',')
joinWithComma(['a', 'b', 'c']) // 'a,b,c'[F] merge
(string1: string, string2: string): stringParameters:
string1- The first stringstring2- The second string
Returns: The concatenated string
DEPRECATED
Use String.concat from Effect instead
Merge two strings together (concatenate).
Examples:
Str.merge('hello', ' world') // 'hello world'
Str.merge('foo', 'bar') // 'foobar'[C] mergeOn
;((string1: string) => (string2: string) => string)Curried version of merge with string1 first.
Examples:
const mergeWithHello = Str.mergeOn('hello')
mergeWithHello(' world') // 'hello world'Type Guards
[F] isEmpty
(value: string): booleanParameters:
value- The string to check
Returns: True if the string is empty
DEPRECATED
Use String.isEmpty from Effect instead
Type guard to check if a string is empty.
Examples:
Str.isEmpty('') // true
Str.isEmpty('hello') // false
Str.isEmpty(' ') // falseType Utilities
[T] Empty
type Empty = ''Type for an empty string.
Type-Level Utilities
[T] GetKindCase
type GetKindCase<$S extends string> = string extends $S ? 'string' : 'literal'Determine if a string type is a literal or the generic string type.
Returns 'literal' for concrete string literals, 'string' for the string type. Template literals with string interpolations will be detected by the consuming utilities during their normal computation and will return number.
Useful for discriminated type branching with indexed access patterns.
Examples:
// Discriminated branching pattern
type Result<$S extends string> = {
string: number
literal: ComputeExactValue<$S>
}[Str.GetKindCase<$S>]
type R1 = Result<'hello'> // ComputeExactValue<'hello'>
type R2 = Result<string> // number
type R3 = Result<`prefix-${string}`> // number (detected during computation)[T] EndsWith
type EndsWith<S extends string, T extends string> = S extends `${string}${T}`
? true
: falseCheck if a string ends with a specific suffix.
[T] StartsWith
type StartsWith<S extends string, T extends string> = S extends `${T}${string}`
? true
: falseCheck if a string starts with a specific prefix.
[T] LastSegment
type LastSegment<S extends string> = S extends `${string}/${infer Rest}`
? LastSegment<Rest>
: SExtract the last segment from a path-like string (after the last '/').
[T] RemoveTrailingSlash
type RemoveTrailingSlash<S extends string> = S extends `${infer Rest}/`
? Rest extends '' ? '/' : Rest
: SRemove trailing slash from a string.
[T] Split
type Split<S extends string, D extends string, Acc extends string[] = []> =
S extends '' ? Acc
: S extends `${infer Segment}${D}${infer Rest}`
? Segment extends '' ? Split<Rest, D, Acc>
: Segment extends '.' ? Split<Rest, D, Acc>
: Split<Rest, D, [...Acc, Segment]>
: S extends '.' ? Acc
: [...Acc, S]Split a string by a delimiter, filtering out empty segments and '.' segments. This is useful for path-like strings.
[T] Contains
type Contains<S extends string, C extends string> = S extends
`${string}${C}${string}` ? true : falseCheck if string contains a character.
Type-Level Utilities $S - The string to measure $AllowSlow - Local override for allowSlow setting (defaults to global setting)
[T] Length
type Length<
$S extends string,
$AllowSlow extends boolean = KitLibrarySettings.Perf.Settings['allowSlow'],
> = {
string: number
literal: LengthFast<$S> extends never
? NormalizeAllowSlow<$AllowSlow> extends true ? LengthSlow<$S>
: Ts.Err.StaticError<
'String length exceeds fast path limit (20 chars)',
{
hint:
'Pass true as second parameter or set KitLibrarySettings.Perf.Settings.allowSlow to true'
limit: '0-20 chars (fast) | 21-4000 chars (slow, opt-in)'
received: $S
}
>
: LengthFast<$S>
}[GetKindCase<$S>]Get the length of a string literal type.
For non-literal strings (type string), returns number. For literal strings, returns exact length or error based on settings.
Performance characteristics:
0-20 chars: Instant evaluation via pattern matching lookup table (6-362 instantiations)
21-4000 chars: Requires KitLibrarySettings.Perf.Settings.allowSlow flag or local override
When enabled: Uses tail-recursive 4x unrolling (597-2053 instantiations)
Limit: ~4000 chars (1000 tail recursion limit × 4 chars/recursion)
When disabled: Returns helpful error with instructions to enable
4000+ chars: Exceeds TypeScript's tail recursion depth limit, will fail
Non-literal (
string): Returnsnumber(cannot determine length at compile time)
Implementation details: The 4000 character limit is specific to this utility's 4x unrolling strategy. Other utilities may have different limits based on their unrolling factor and type complexity. Fast path covers 95% of real-world use cases with zero performance cost.
Examples:
// Fast path (instant)
type L1 = Str.Length<'hello'> // 5
type L2 = Str.Length<''> // 0
type L3 = Str.Length<'a'> // 1
// Non-literal string
type L4 = Str.Length<string> // number
// Exceeds fast path without flag
type L5 = Str.Length<'this string is over 20 chars long'>
// Error: String length exceeds fast path limit (20 chars)
// Set KitLibrarySettings.Perf.Settings.allowSlow to true
// Local override - no global setting needed
type L6 = Str.Length<'this string is over 20 chars long', true> // 38 (works, slower compilation)
// With global allowSlow flag enabled
declare global {
namespace KitLibrarySettings {
namespace Perf {
interface Settings {
allowSlow: true
}
}
}
}
type L7 = Str.Length<'this string is over 20 chars long'> // 38 (works, slower compilation)Type-Level Utilities $S - The string to pad $TargetLen - The desired final length $Fill - The character to use for padding (default: '_') $Acc - Accumulator for recursion depth tracking (internal)
[T] PadEnd
type PadEnd<
$S extends string,
$TargetLen extends number,
$Fill extends string = '_',
$Acc extends 0[] = [],
> = Length<$S> extends $TargetLen ? $S
: $Acc['length'] extends 50 // Recursion limit safety
? $S
: PadEnd<`${$S}${$Fill}`, $TargetLen, $Fill, [...$Acc, 0]>Pad a string to a target length by appending a fill character.
If the string is already at or exceeds the target length, returns it unchanged. Limited by TypeScript's recursion depth (~50 iterations).
Examples:
type P1 = Str.PadEnd<'foo', 10, '_'> // 'foo_______'
type P2 = Str.PadEnd<'hello', 3, '_'> // 'hello' (already longer)
type P3 = Str.PadEnd<'abc', 5, '0'> // 'abc00'Type-Level Utilities $S - The string to pad $TargetLen - The desired final length $Fill - The character to use for padding (default: '0') $Acc - Accumulator for recursion depth tracking (internal)
[T] PadStart
type PadStart<
$S extends string,
$TargetLen extends number,
$Fill extends string = '0',
$Acc extends 0[] = [],
> = Length<$S> extends $TargetLen ? $S
: $Acc['length'] extends 50 // Recursion limit safety
? $S
: PadStart<`${$Fill}${$S}`, $TargetLen, $Fill, [...$Acc, 0]>Pad a string to a target length by prepending a fill character.
If the string is already at or exceeds the target length, returns it unchanged. Limited by TypeScript's recursion depth (~50 iterations).
Examples:
type P1 = Str.PadStart<'42', 5, '0'> // '00042'
type P2 = Str.PadStart<'hello', 3, '0'> // 'hello' (already longer)
type P3 = Str.PadStart<'x', 3, ' '> // ' x'Type-Level Utilities T - The string type to check $ErrorMessage - Custom error message to display when T is not a literal
[T] LiteralOnly
type LiteralOnly<
T extends string,
$ErrorMessage extends string = 'Expected a literal string',
> = string extends T ? Ts.Err.StaticError<
$ErrorMessage,
{ ReceivedType: T; tip: 'Use a string literal instead of string type' }
>
: TConstraint that only accepts literal strings. Returns StaticError for non-literal string type with customizable error message.