Stacks Strings
Key Paths
- Core package:
storage/framework/core/strings/src/ - Package:
@stacksjs/strings - Entry:
storage/framework/core/strings/src/string.ts(re-exports all submodules)
Architecture
The package re-exports from these submodules:
case.ts— case conversion functions (camelCase, pascalCase, snakeCase, etc.)pluralize.ts— English pluralization/singularization with rule engineslug.ts— URL slug generation with extensive character maputils.ts— random strings, template interpolation, slash, prefix/suffix, truncate, detect-indent, detect-newlinemacro.ts—Strandstrfacade objectsvalidators.ts— re-exports fromis.ts(native string validators)sponge-case.ts— random upper/lower caseswap-case.ts— swap upper/lower casetitle-case.ts— intelligent title case with small word handling
The top-level index.ts does:
export * from './string'
export * as string from './string'
And string.ts does:
export * from './case'
export * from './helpers'
export * from './macro'
export * from './pluralize'
export * from './slug'
export * from './utils'
export * from './validators'
Case Conversion Functions
All accept (input: string, options?: CaseOptions) and use Unicode-aware word boundary detection via regex patterns (\p{Ll}, \p{Lu}).
interface CaseOptions {
locale?: string[] | string | false | undefined
split?: (value: string) => string[]
delimiter?: string
prefixCharacters?: string // characters to preserve as prefix
suffixCharacters?: string // characters to preserve as suffix
}
interface PascalCaseOptions extends CaseOptions {
mergeAmbiguousCharacters?: boolean
}
Functions
camelCase('hello world') // 'helloWorld' — first word lowercase, rest capitalized
pascalCase('hello world') // 'HelloWorld' — all words capitalized
snakeCase('hello world') // 'hello_world' — alias for noCase with '_' delimiter
kebabCase('hello world') // 'hello-world' — alias for noCase with '-' delimiter
constantCase('hello world') // 'HELLO_WORLD' — all uppercase with '_' delimiter
trainCase('hello world') // 'Hello-World' — capitalCase with '-' delimiter
dotCase('hello world') // 'hello.world' — noCase with '.' delimiter
pathCase('hello world') // 'hello/world' — noCase with '/' delimiter
sentenceCase('hello world') // 'Hello world' — first word capitalized, rest lowercase
capitalCase('hello world') // 'Hello World' — all words capitalized, space delimiter
noCase('helloWorld') // 'hello world' — lowercase with space delimiter
pascalSnakeCase('hello world') // 'Hello_World' — capitalCase with '_' delimiter
paramCase('hello world') // 'hello-world' — alias for kebabCase
Special Case Functions
spongeCase('hello world') // 'hElLo wOrLd' — random upper/lower per character
spongeCase('hello', 'en') // accepts optional locale
swapCase('Hello') // 'hELLO' — swaps each character's case
swapCase('Hello', 'en') // accepts optional locale
Title Case
titleCase('hello world') // 'Hello World'
titleCase('the quick brown fox') // 'The Quick Brown Fox'
titleCase('the quick brown fox', { sentenceCase: true }) // 'The quick brown fox'
interface TitleCaseOptions {
locale?: string | string[]
sentenceCase?: boolean // only capitalize first word + after terminators
sentenceTerminators?: Set<string> // default: '.', '!', '?'
titleTerminators?: Set<string> // default: '.', '!', '?', ':', '"', "'", '"'
smallWords?: Set<string> // words not capitalized (see SMALL_WORDS)
wordSeparators?: Set<string> // default: '---', '--', '-', '---', '/'
}
Handles special cases: URLs, email addresses, acronyms (e.g., "U.S.A."), camelCase words (e.g., "iPhone"), and hyphenated compound words.
Word Splitting
split('camelCase') // ['camel', 'Case']
split('hello_world') // ['hello', 'world']
split('HTTPSRequest') // ['HTTPS', 'Request']
splitSeparateNumbers('test123') // ['test', '123']
Uses Unicode-aware regex for word boundary detection. Handles camelCase, PascalCase, snake_case, UPPER_CASE, and mixed formats.
Pluralization
Full English pluralization engine with irregular words, custom rules, and uncountable nouns.
import { plural, singular, pluralize } from '@stacksjs/strings'
// Basic usage
plural('person') // 'people'
plural('person', 1) // 'person' (count-aware)
singular('people') // 'person'
// Pluralize function with options
pluralize('mouse') // 'mice'
pluralize('mouse', { count: 1 }) // 'mouse'
pluralize('mouse', { count: 2 }) // 'mice'
pluralize('mouse', { count: 5, inclusive: true }) // '5 mice'
// Boolean checks
pluralize.isPlural('dogs') // true
pluralize.isSingular('dog') // true
// Custom rules
pluralize.addPluralRule(/gex$/i, 'gices')
pluralize.addSingularRule(/gices$/i, 'gex')
pluralize.addIrregularRule('person', 'people')
pluralize.addUncountableRule('sheep')
Built-in Irregular Words
The engine includes 48 irregular word pairs (e.g., ox/oxen, foot/feet, goose/geese, child/children, person/people, die/dice, tooth/teeth, quiz/quizzes, etc.).
Built-in Uncountable Words
Includes 74 uncountable words (e.g., sheep, fish, moose, aircraft, software, equipment, information, etc.) plus regex patterns for Pokemon, words ending in non-ASCII ese, deer, fish, measles, pox, sheep.
Case Preservation
The pluralization engine preserves the case pattern of the input word:
'PERSON'->'PEOPLE''Person'->'People''person'->'people'
Slug Generation
Two slug functions with different defaults:
slug(str, options?) — Simple slug (strict + lowercase by default)
slug('Hello World') // 'hello-world'
slug('Hello World', { lower: false }) // 'Hello-World'
slugify(str, options?) — Full-featured slug
slugify('Hello World!') // 'Hello-World!' (not strict/lowercase by default)
slugify('Hello World!', { lower: true, strict: true }) // 'hello-world'
interface SlugifyOptions {
replacement?: string // default: '-'
remove?: RegExp // chars to remove (default: /[^\w\s$*+~.()'"!\-:@]+/g)
lower?: boolean // default: false
strict?: boolean // strip non-alphanumeric (default: false)
locale?: string // locale-specific char mapping
trim?: boolean // trim result (default: true)
}
Character Map
Extensive built-in character map covering:
- Latin extended (accented chars): A-Z with diacritics
- Greek alphabet
- Cyrillic alphabet
- Armenian alphabet
- Arabic alphabet and numerals
- Georgian alphabet
- Vietnamese diacritics
- Currency symbols ($, EUR, GBP, YEN, etc.)
- Special characters (trademark, copyright, etc.)
Locale Support
Locale-specific character mappings for: bg, de, es, fr, pt, uk, vi, da, nb, it, nl, sv.
slugify('AE', { locale: 'de' }) // 'AE' (German)
slugify('AE') // 'A' (default)
Extending the Character Map
extendCharMap({ 'EUR': 'EUR', 'GBP': 'GBP' })
String Utilities
capitalize('hello world') // 'Hello world' — first char upper, rest lower
lowercase('HELLO') // 'hello'
slash('path\\to\\file') // 'path/to/file'
ensurePrefix('https://', 'google.com') // 'https://google.com'
ensurePrefix('https://', 'https://x.com') // 'https://x.com' (already has prefix)
ensureSuffix('.ts', 'file') // 'file.ts'
ensureSuffix('.ts', 'file.ts') // 'file.ts' (already has suffix)
template('Hello {0}! My name is {1}.', 'Buddy', 'Chris') // 'Hello Buddy! My name is Chris.'
truncate('long text here', 10, '...') // 'long te...'
truncate('short', 10) // 'short' (no truncation needed)
random() // 16-char random string (nanoid-style)
random(21) // 21-char random string
random(10, 'abc') // 10-char string from custom alphabet
urlAlphabet Constant
// 64 URL-safe characters used by random() by default
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
String Validators
Native TypeScript implementations (no external validator dependency). All accept a string and return boolean.
Core Validators
isEmail('user@example.com') // true -- regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
isStrongPassword('Abc123!@#') // true -- min 8 chars, upper, lower, digit, symbol
isAlphanumeric('abc123') // true -- /^[a-zA-Z0-9]+$/
isAlpha('abcdef') // true -- /^[a-zA-Z]+$/
isNumeric('123') // true -- /^[0-9]+$/ (whole numbers only)
isURL('https://example.com') // true -- uses URL constructor, requires http(s) protocol
Format Validators
isHexColor('#ff0000') // true -- 3 or 6 hex digits with #
isHexadecimal('deadbeef') // true -- hex chars only
isBase64('SGVsbG8=') // true -- btoa(atob(x)) === x
isBase32('MFZWI===') // true -- /^[A-Z2-7]+=*$/
isAscii('hello') // true -- /^[\x00-\x7F]*$/
isJSON('{"key":"value"}') // true -- JSON.parse succeeds
isHSL('hsl(0, 100%, 50%)') // true
isMimeType('text/html') // true -- /^[a-z]+\/[a-z0-9\-+.]+$/i
isDataURI('data:text/plain;base64,...') // true
isJWT('eyJhbGciOi...') // true -- 3 dot-separated base64url segments
Network Validators
isIP('192.168.1.1') // true -- IPv4 and IPv6
isIPRange('192.168.1.0/24') // true -- CIDR notation (IPv4: 0-32, IPv6: 0-128)
isMACAddress('00:11:22:33:44:55') // true -- colon or hyphen separated
isFQDN('example.com') // true -- fully qualified domain name
Financial Validators
isCreditCard('4111111111111111') // true -- Luhn algorithm, 13-19 digits
isISBN('978-3-16-148410-0') // true -- ISBN-10 and ISBN-13 with checksum
isCurrency('$1,234.56') // true -- supports $, GBP, EUR, YEN
isIBAN('DE89370400440532013000') // true -- mod 97 check
isISIN('US0378331005') // true -- Luhn algorithm
isISSN('0378-5955') // true -- mod 11 check
Geographic Validators
isLatLong('40.7128,-74.0060') // true -- lat: -90..90, long: -180..180
isLatitude('40.7128') // true
isLongitude('-74.0060') // true
isPostalCode('12345') // true -- US, UK, Canada, generic formats
Phone, Identity, Date Validators
isMobilePhone('+1234567890') // true -- international format, min 7 digits
isUUID('550e8400-e29b-41d4-a716...') // true -- v1-v5
isISO8601('2024-01-01T00:00:00Z') // true -- with Date validity check
isIdentityCard('AB123456') // true -- generic 5-20 alphanumeric
isISRC('USRC17607839') // true
isISO31661Alpha2('US') // true
isISO31661Alpha3('USA') // true
validateUsername('user123') // true -- alias for isAlphanumeric
isHash('abc123...', 'md5') // true -- supports md5(32), sha1(40), sha256(64), sha384(96), sha512(128)
isByteLength('hello', { min: 1, max: 10 }) // true
isFullWidth('\uFF21') // true
isHalfWidth('a') // true
Newline & Indentation Detection
Newline Detection (from detect-newline.ts)
detectNewline('line1\r\nline2') // '\r\n'
detectNewline('line1\nline2') // '\n'
detectNewline('no newline') // undefined
detectNewlineGraceful(text) // '\n' (fallback when no newline found)
The detection counts CRLF vs LF occurrences and returns the dominant type.
Indentation Detection (from detect-indent.ts)
detectIndent(' hello\n world')
// { amount: 2, type: 'space', indent: ' ' }
detectIndent('\thello\n\t\tworld')
// { amount: 1, type: 'tab', indent: '\t' }
detectIndent('no indent')
// { amount: 0, type: undefined, indent: '' }
Algorithm: builds a frequency map of indent changes (type + size), handles edge cases like single-space indentation (skipped by default to avoid false positives from code comments), then selects the most common indent pattern.
Str Facade Object
Both Str and str are exported as equivalent facade objects providing all functions as methods.
import { Str, str } from '@stacksjs/strings'
// Case conversion
Str.camelCase('hello world') // 'helloWorld'
Str.pascalCase('hello world') // 'HelloWorld'
Str.snakeCase('hello world') // 'hello_world'
Str.kebabCase('hello world') // 'hello-world'
Str.constantCase('hello world') // 'HELLO_WORLD'
Str.dotCase('hello world') // 'hello.world'
Str.noCase('helloWorld') // 'hello world'
Str.paramCase('hello world') // 'hello-world'
Str.pathCase('hello world') // 'hello/world'
Str.sentenceCase('hello world') // 'Hello world'
Str.capitalCase('hello world') // 'Hello World'
Str.titleCase('hello world') // 'Hello World'
// Utilities
Str.capitalize('hello') // 'Hello'
Str.slug('Hello World') // 'hello-world'
Str.random(16) // random string
Str.random(10, 'abc') // random from custom dict
Str.template('{0} {1}', 'a', 'b') // 'a b'
Str.truncate('long text', 5) // 'lo...'
Str.slash('path\\to') // 'path/to'
Str.ensurePrefix('/', 'path') // '/path'
Str.ensureSuffix('.ts', 'file') // 'file.ts'
Str.detectIndent(text) // { amount, type, indent }
Str.detectNewline(text) // '\n' | '\r\n' | undefined
// Pluralization
Str.plural('person') // 'people'
Str.singular('people') // 'person'
Str.isPlural('dogs') // true
Str.isSingular('dog') // true
Str.addPluralRule(/ox$/i, 'oxen')
Str.addSingularRule(/oxen$/i, 'ox')
Str.addIrregularRule('goose', 'geese')
Str.addUncountableRule('sheep')
Exported Constants
// From title-case.ts
WORD_SEPARATORS: Set<string> // '---', '--', '-', '---', '/'
SENTENCE_TERMINATORS: Set<string> // '.', '!', '?'
TITLE_TERMINATORS: Set<string> // '.', '!', '?', ':', '"', "'", '"'
SMALL_WORDS: Set<string> // 'a', 'an', 'and', 'as', 'at', 'because', 'but', 'by',
// 'en', 'for', 'if', 'in', 'neither', 'nor', 'of', 'on',
// 'only', 'or', 'over', 'per', 'so', 'some', 'than', 'that',
// 'the', 'to', 'up', 'upon', 'v', 'versus', 'via', 'vs',
// 'when', 'with', 'without', 'yet'
// From utils.ts
urlAlphabet: string // 64 URL-safe chars for random()
Exported Types
type Locale = string[] | string | false | undefined
interface CaseOptions { locale?, split?, delimiter?, prefixCharacters?, suffixCharacters? }
interface PascalCaseOptions extends CaseOptions { mergeAmbiguousCharacters? }
interface TitleCaseOptions { locale?, sentenceCase?, sentenceTerminators?, smallWords?, titleTerminators?, wordSeparators? }
interface SlugifyOptions { replacement?, remove?, lower?, strict?, locale?, trim? }
interface PluralizeOptions { count?, inclusive? }
Gotchas
- Case conversion uses Unicode-aware word boundary detection (
\p{Ll},\p{Lu}) -- handles international characters slug()callsslugify()under the hood but defaults to{ lower: true, strict: true };slugify()defaults to{ lower: false, strict: false }pascalCaseandcamelCaseacceptPascalCaseOptionswithmergeAmbiguousCharactersto control number-prefixed word handling- All case functions support
prefixCharacters/suffixCharactersto preserve leading/trailing non-word characters pluralize()is both a function AND an object with methods --pluralize.plural(),pluralize.singular(), etc.- The
random()function usesMath.random(), not crypto -- fine for IDs but not for security tokens isNumeric()only matches whole numbers (/^\d+$/), not floats -- use parseFloat for float checkingisURL()requireshttp:orhttps:protocol -- rejects other protocols and bare domains- The
Strfacade does NOT include validators -- only case, utility, and pluralization methods helpers.tsonly exportstoString()which returnsObject.prototype.toString.call(v)