nano-string-utils
Version:
Modern string utilities with zero dependencies. Tree-shakeable (<1KB each), TypeScript-first, type-safe. Validation, XSS prevention, case conversion, fuzzy matching & more.
1,548 lines (1,496 loc) • 95 kB
TypeScript
/**
* Converts a string to a URL-safe slug
* @param str - The input string to slugify
* @returns A URL-safe slug
* @example
* ```ts
* // Basic usage
* slugify('Hello World!') // 'hello-world'
* slugify(' Multiple Spaces ') // 'multiple-spaces'
* slugify('Special #@! Characters') // 'special-characters'
*
* // Using with branded Slug type for type safety
* import { toSlug, ensureSlug, type Slug } from 'nano-string-utils/types'
*
* // Create type-safe slugs
* const slug: Slug = toSlug('Blog Post Title!')
* // slug is 'blog-post-title' with Slug branded type
*
* // Type-safe routing
* interface Route {
* path: Slug
* component: React.FC
* }
*
* const routes: Route[] = [
* { path: toSlug('About Us'), component: AboutPage },
* { path: toSlug('Contact'), component: ContactPage }
* ]
*
* // Ensure slug with validation
* const userInput = 'already-slugified'
* const slug2 = ensureSlug(userInput)
* // If already a valid slug, returns as-is; otherwise slugifies
*
* // Database schema with branded types
* interface BlogPost {
* id: string
* title: string
* slug: Slug // Ensures only valid slugs in DB
* }
*
* const createPost = (title: string): BlogPost => ({
* id: generateId(),
* title,
* slug: toSlug(title)
* })
* ```
*/
declare const slugify: (str: string) => string;
/**
* Truncates a string to a specified length and adds ellipsis
* @param str - The input string to truncate
* @param length - The maximum length of the output string (including ellipsis)
* @param suffix - The suffix to append (default: '...')
* @returns A truncated string with ellipsis
* @example
* ```ts
* // Basic usage
* truncate('Long text here', 10) // 'Long te...'
* truncate('Short', 10) // 'Short'
*
* // Function overloads for different signatures
* const text = 'This is a very long string that needs truncation'
* truncate(text, 20) // Uses default '...' suffix
* truncate(text, 20, '…') // Custom suffix with single character
* truncate(text, 25, ' [more]') // Custom suffix with text
*
* // Strict null checking in TypeScript
* const maybeString: string | null = getUserInput()
* if (maybeString) {
* // TypeScript knows maybeString is string here
* const truncated = truncate(maybeString, 50)
* }
*
* // Safe handling with nullish coalescing
* const description = truncate(product.description ?? '', 100)
*
* // Type-safe truncation in components
* interface CardProps {
* title: string
* body: string | undefined
* }
* function Card({ title, body }: CardProps) {
* return (
* <div>
* <h3>{truncate(title, 50)}</h3>
* <p>{body ? truncate(body, 200) : 'No description'}</p>
* </div>
* )
* }
*
* // Emoji-safe truncation (handles complex graphemes)
* truncate('Hello 👋 World 🌍!', 10) // 'Hello 👋...'
* truncate('Family 👨👩👧👦 photo', 12) // 'Family 👨👩👧👦...' (ZWJ emoji stays intact)
* truncate('café résumé', 8) // 'café...' (diacritics preserved)
* ```
*/
declare function truncate(str: string, length: number): string;
declare function truncate(str: string, length: number, suffix: string): string;
/**
* Capitalizes the first letter of a string and lowercases the rest
* @param str - The input string to capitalize
* @returns A string with the first letter capitalized and rest lowercased
* @example
* ```ts
* // Basic usage
* capitalize('hello world') // 'Hello world'
* capitalize('HELLO') // 'Hello'
*
* // Form labels and UI text
* const label = capitalize('email address') // 'Email address'
* const field = capitalize('firstName') // 'Firstname'
*
* // Sentence starting in text editors
* const sentence = capitalize('the quick brown fox...') // 'The quick brown fox...'
*
* // CSV header normalization
* const headers = ['NAME', 'EMAIL', 'PHONE'].map(capitalize)
* // ['Name', 'Email', 'Phone']
* ```
*/
declare const capitalize: (str: string) => string;
// Helper to join array of strings with delimiter
type Join<T extends readonly string[], D extends string> = T extends readonly []
? ""
: T extends readonly [string]
? T[0]
: T extends readonly [string, ...infer R]
? R extends readonly string[]
? `${T[0]}${D}${Join<R, D>}`
: never
: string;
// Capitalize first letter
type CapitalizeFirst<S extends string> = S extends `${infer F}${infer R}`
? `${Uppercase<F>}${R}`
: S;
// Helper to check if a character is a digit
type IsDigit<C extends string> = C extends
| "0"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
? true
: false;
// Helper to check if a character is a letter
type IsLetter<C extends string> = Uppercase<C> extends Lowercase<C>
? false
: true;
// Helper to check if string is all digits
type IsAllDigits<S extends string> = S extends ""
? false
: S extends `${infer C}${infer Rest}`
? IsDigit<C> extends true
? Rest extends ""
? true
: IsAllDigits<Rest>
: false
: false;
// Helper to check if string contains lowercase letters
type HasLowercase<S extends string> = Lowercase<S> extends S
? Uppercase<S> extends S
? false
: true
: false;
// Simplified word splitter that preserves all-caps words and groups consecutive digits
type SplitWords<S extends string, Acc extends string = ""> = S extends ""
? Acc extends ""
? []
: [Acc]
: S extends `${infer C}${infer Rest}`
? // Non-letter characters are word boundaries
IsLetter<C> extends false
? IsDigit<C> extends true
? // Current char is a digit
IsAllDigits<Acc> extends true
? // Accumulator has only digits, continue accumulating
SplitWords<Rest, `${Acc}${C}`>
: Acc extends ""
? // Start new digit sequence
SplitWords<Rest, C>
: // Acc has letters, flush and start digit sequence
[Acc, ...SplitWords<Rest, C>]
: // Non-letter, non-digit - word boundary
Acc extends ""
? SplitWords<Rest, "">
: [Acc, ...SplitWords<Rest, "">]
: // Letter character
IsAllDigits<Acc> extends true
? // Acc has digits, flush and start letter sequence
[Acc, ...SplitWords<`${C}${Rest}`, "">]
: C extends Uppercase<C>
? // Current character is uppercase
Acc extends ""
? // No accumulator, start new word with uppercase
Rest extends `${infer Next}${infer After}`
? Next extends Lowercase<Next>
? IsLetter<Next> extends true
? // Uppercase followed by lowercase - capture both
SplitWords<After, `${C}${Next}`>
: SplitWords<Rest, C>
: SplitWords<Rest, C>
: [C]
: // Have accumulator
HasLowercase<Acc> extends true
? // Accumulator has lowercase, this uppercase starts new word
Rest extends `${infer Next}${infer After}`
? Next extends Lowercase<Next>
? IsLetter<Next> extends true
? // Uppercase followed by lowercase - new word
[Acc, ...SplitWords<After, `${C}${Next}`>]
: [Acc, ...SplitWords<Rest, C>]
: [Acc, ...SplitWords<Rest, C>]
: [Acc, C]
: // Accumulator is all uppercase
Rest extends `${infer Next}${infer After}`
? Next extends Lowercase<Next>
? IsLetter<Next> extends true
? // CAPS followed by lowercase - new word starts at current uppercase
Acc extends `${infer AccPrefix}${string}`
? AccPrefix extends ""
? SplitWords<After, `${C}${Next}`>
: [Acc, ...SplitWords<After, `${C}${Next}`>]
: [Acc, ...SplitWords<After, `${C}${Next}`>]
: SplitWords<Rest, `${Acc}${C}`>
: // Uppercase followed by uppercase - continue caps word
SplitWords<Rest, `${Acc}${C}`>
: [`${Acc}${C}`]
: // Lowercase letter - continue current word
SplitWords<Rest, `${Acc}${C}`>
: [];
// Helper to extract words from any case
type ExtractWords<S extends string> = string extends S
? string[]
: S extends `${infer W}-${infer R}`
? [...SplitWords<W>, ...ExtractWords<R>]
: S extends `${infer W}_${infer R}`
? [...SplitWords<W>, ...ExtractWords<R>]
: S extends `${infer W} ${infer R}`
? [...SplitWords<W>, ...ExtractWords<R>]
: S extends `${infer W}.${infer R}`
? [...SplitWords<W>, ...ExtractWords<R>]
: S extends `${infer W}/${infer R}`
? [...SplitWords<W>, ...ExtractWords<R>]
: SplitWords<S>;
// Helper to transform word arrays
type TransformWords<
Words extends readonly string[],
Transform extends "lower" | "upper" | "capital"
> = Words extends readonly []
? []
: Words extends readonly [infer H, ...infer R]
? H extends string
? R extends readonly string[]
? Transform extends "lower"
? [Lowercase<H>, ...TransformWords<R, Transform>]
: Transform extends "upper"
? [Uppercase<H>, ...TransformWords<R, Transform>]
: [CapitalizeFirst<Lowercase<H>>, ...TransformWords<R, Transform>]
: []
: []
: [];
/**
* Convert string to camelCase at type level
* @example
* type Result = CamelCase<"hello-world"> // "helloWorld"
*/
type CamelCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly [infer F, ...infer R]
? F extends string
? R extends readonly string[]
? `${Lowercase<F>}${Join<TransformWords<R, "capital">, "">}`
: Lowercase<S>
: ""
: "";
/**
* Convert string to kebab-case at type level
* @example
* type Result = KebabCase<"helloWorld"> // "hello-world"
*/
type KebabCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "lower">, "-">
: string;
/**
* Convert string to snake_case at type level
* @example
* type Result = SnakeCase<"helloWorld"> // "hello_world"
*/
type SnakeCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "lower">, "_">
: string;
/**
* Convert string to PascalCase at type level
* @example
* type Result = PascalCase<"hello-world"> // "HelloWorld"
*/
type PascalCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "capital">, "">
: string;
/**
* Convert string to CONSTANT_CASE at type level
* @example
* type Result = ConstantCase<"helloWorld"> // "HELLO_WORLD"
*/
type ConstantCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "upper">, "_">
: string;
/**
* Convert string to dot.case at type level
* @example
* type Result = DotCase<"helloWorld"> // "hello.world"
*/
type DotCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "lower">, ".">
: string;
/**
* Convert string to path/case at type level
* @example
* type Result = PathCase<"helloWorld"> // "hello/world"
*/
type PathCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "lower">, "/">
: string;
/**
* Convert string to Sentence case at type level
* @example
* type Result = SentenceCase<"helloWorld"> // "Hello world"
*/
type SentenceCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly [infer F, ...infer R]
? F extends string
? R extends readonly string[]
? `${CapitalizeFirst<Lowercase<F>>}${R extends readonly []
? ""
: " "}${Join<TransformWords<R, "lower">, " ">}`
: CapitalizeFirst<Lowercase<S>>
: ""
: "";
/**
* Convert string to Title Case at type level
* @example
* type Result = TitleCase<"hello-world"> // "Hello World"
*/
type TitleCase<S extends string> = string extends S
? string
: ExtractWords<S> extends readonly string[]
? Join<TransformWords<ExtractWords<S>, "capital">, " ">
: string;
/**
* Converts a string to camelCase
* @param str - The input string to convert
* @returns A camelCase string
* @example
* ```ts
* // Basic usage
* camelCase('hello world') // 'helloWorld'
* camelCase('hello-world') // 'helloWorld'
* camelCase('hello_world') // 'helloWorld'
* camelCase('HELLO_WORLD') // 'helloWorld'
*
* // TypeScript template literal types
* const result = camelCase('hello-world')
* // result type: "helloWorld" (literal type)
*
* // Type inference with const assertions
* const input = 'user-profile' as const
* const output = camelCase(input)
* // output type: "userProfile" (literal type preserved)
*
* // Works with function parameters
* function processField<T extends string>(field: T): CamelCase<T> {
* return camelCase(field) as CamelCase<T>
* }
* const fieldName = processField('first-name')
* // fieldName type: "firstName"
* ```
*/
declare function camelCase<T extends string>(str: T): CamelCase<T>;
declare function camelCase(str: string): string;
/**
* Converts a string to snake_case
* @param str - The input string to convert
* @returns A snake_case string
* @example
* ```ts
* // Basic usage
* snakeCase('Hello World') // 'hello_world'
* snakeCase('helloWorld') // 'hello_world'
* snakeCase('hello-world') // 'hello_world'
* snakeCase('HelloWorld') // 'hello_world'
*
* // TypeScript template literal types
* const result = snakeCase('helloWorld')
* // result type: "hello_world" (literal type)
*
* // Database column naming with type safety
* type EntityField = 'firstName' | 'lastName' | 'createdAt'
* function getColumnName(field: EntityField): SnakeCase<EntityField> {
* return snakeCase(field) as SnakeCase<EntityField>
* }
* const column = getColumnName('firstName')
* // column type: "first_name"
*
* // Python interop
* const pythonVar = snakeCase('jsVariable' as const)
* // pythonVar type: "js_variable"
* ```
*/
declare function snakeCase<T extends string>(str: T): SnakeCase<T>;
declare function snakeCase(str: string): string;
/**
* Converts a string to kebab-case
* @param str - The input string to convert
* @returns A kebab-case string
* @example
* ```ts
* // Basic usage
* kebabCase('Hello World') // 'hello-world'
* kebabCase('helloWorld') // 'hello-world'
* kebabCase('hello_world') // 'hello-world'
* kebabCase('HelloWorld') // 'hello-world'
*
* // TypeScript template literal types
* const result = kebabCase('helloWorld')
* // result type: "hello-world" (literal type)
*
* // CSS class name generation with type safety
* type ComponentProps = 'primaryButton' | 'secondaryButton'
* function getClassName(prop: ComponentProps): KebabCase<ComponentProps> {
* return kebabCase(prop) as KebabCase<ComponentProps>
* }
* const className = getClassName('primaryButton')
* // className type: "primary-button"
*
* // API endpoint generation
* const endpoint = kebabCase('getUserProfile' as const)
* // endpoint type: "get-user-profile"
* const url = `/api/${endpoint}` // type: "/api/get-user-profile"
* ```
*/
declare function kebabCase<T extends string>(str: T): KebabCase<T>;
declare function kebabCase(str: string): string;
/**
* Removes HTML tags from a string
* @param str - The input string containing HTML
* @returns A string with HTML tags removed
* @example
* ```ts
* // Basic usage
* stripHtml('<p>Hello <b>World</b></p>') // 'Hello World'
* stripHtml('<div>Test</div>') // 'Test'
*
* // Email preview text generation
* const emailBody = '<h1>Welcome!</h1><p>Thanks for signing up.</p>'
* const preview = stripHtml(emailBody) // 'Welcome! Thanks for signing up.'
*
* // Notification sanitization
* const notification = '<script>alert("xss")</script><p>New message</p>'
* const safeText = stripHtml(notification) // 'alert("xss")New message'
*
* // Blog post excerpt for meta description
* const post = '<article><p>This is a <strong>great</strong> post.</p></article>'
* const excerpt = stripHtml(post).slice(0, 160) // 'This is a great post.'
*
* // Search indexing - extract text content
* const htmlContent = '<div class="content"><p>Searchable text</p></div>'
* const indexableText = stripHtml(htmlContent) // 'Searchable text'
* ```
*/
declare const stripHtml: (str: string) => string;
/**
* Escapes HTML special characters in a string to prevent XSS attacks
* @param str - The input string to escape
* @returns A string with HTML entities escaped
* @example
* ```ts
* // Basic usage
* escapeHtml('<div>Hello & "World"</div>')
* // '<div>Hello & "World"</div>'
*
* // XSS prevention in user input
* const userInput = '<script>alert("xss")</script>'
* const safe = escapeHtml(userInput)
* // '<script>alert("xss")</script>'
*
* // Displaying user-generated content safely
* const comment = userComment.replace(/</g, '<')
* // Better: const safe = escapeHtml(userComment)
* document.getElementById('comment').innerHTML = escapeHtml(userComment)
*
* // Template safety (use with template strings)
* const name = '<img src=x onerror=alert(1)>'
* const html = `<div>Hello ${escapeHtml(name)}</div>`
* // '<div>Hello <img src=x onerror=alert(1)></div>'
*
* // Escaping attributes
* const title = 'Click "here" & learn more'
* const attr = `<button title="${escapeHtml(title)}">Click</button>`
* // Safe attribute value
* ```
*/
declare const escapeHtml: (str: string) => string;
/**
* Creates a smart excerpt from text with word boundary awareness
* @param text - The text to create an excerpt from
* @param length - Maximum length of the excerpt (approximate)
* @param suffix - Suffix to append when text is truncated (default: '...')
* @returns The excerpted text
* @example
* ```ts
* // Basic usage with word boundary preservation
* excerpt('The quick brown fox jumps over the lazy dog', 20)
* // 'The quick brown fox...' (cuts at word boundary)
*
* // Smart punctuation handling
* excerpt('Hello world. This is a test.', 15)
* // 'Hello world..' (preserves sentence end)
*
* // Function overloads with custom suffix
* excerpt('Long article content here', 50) // Default '...' suffix
* excerpt('Long article content here', 50, '…') // Unicode ellipsis
* excerpt('Long article content here', 50, ' [more]') // Custom text
*
* // Type-safe blog post previews
* interface BlogPost {
* title: string
* content: string
* published: Date
* }
* function getPostPreview(post: BlogPost): string {
* return excerpt(post.content, 150, '...')
* }
*
* // Search result snippets
* interface SearchHit {
* document: string
* score: number
* }
* const results: SearchHit[] = search(query)
* const snippets = results.map(hit => ({
* ...hit,
* snippet: excerpt(hit.document, 200)
* }))
*
* // Null-safe excerpting
* const description: string | null = getDescription()
* const preview = description ? excerpt(description, 100) : 'No description available'
*
* // Smart handling of edge cases
* excerpt('SingleLongWordWithoutSpaces', 10) // 'SingleLong...'
* excerpt('Word', 10) // 'Word' (no truncation needed)
* excerpt('Sentence ending here.', 21) // 'Sentence ending here..' (smart punctuation)
* ```
*/
declare function excerpt(text: string, length: number): string;
declare function excerpt(text: string, length: number, suffix: string): string;
/**
* Generates a random alphanumeric string
* @param length - The length of the random string
* @param charset - Optional character set to use (default: alphanumeric)
* @returns A random string of specified length
* @example
* ```ts
* // Basic usage
* randomString(10) // 'a3B9x2Kp1m'
* randomString(5, 'abc123') // '3a1bc'
*
* // API key generation
* const apiKey = `sk_${randomString(32)}`
* // 'sk_X8mK2pL9vQwR3tY6uZ1aB4cD5eF7gH8i'
*
* // Session ID generation
* const sessionId = randomString(64)
* // Store in cookie or database
*
* // CSRF token generation
* const csrfToken = randomString(40)
* // Use in forms for security
*
* // Test fixture IDs
* const userId = `user_${randomString(12)}`
* const orderId = `order_${randomString(16)}`
*
* // Short codes and invite codes
* const inviteCode = randomString(8, 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789')
* // 'K3P9M7Q2' (omits confusing characters like 0/O, 1/I)
*
* // File upload temporary names
* const tempFile = `upload_${randomString(16)}.tmp`
* ```
*/
declare function randomString(length: number): string;
declare function randomString(length: number, charset: string): string;
/**
* Generates a simple hash from a string using FNV-1a algorithm (non-cryptographic)
* @param str - The input string to hash
* @returns A numeric hash value
* @example
* ```ts
* // Basic usage
* hashString('hello') // 1335831723
* hashString('world') // 3582672807
*
* // Cache key generation
* const cacheKey = hashString(JSON.stringify(queryParams))
* cache.set(cacheKey, result)
*
* // Map key for complex objects
* const userKey = hashString(`${user.id}:${user.email}`)
* const userMap = new Map<number, User>()
* userMap.set(userKey, user)
*
* // Simple deduplication
* const seen = new Set<number>()
* for (const item of items) {
* const hash = hashString(item.content)
* if (!seen.has(hash)) {
* seen.add(hash)
* uniqueItems.push(item)
* }
* }
*
* // Quick checksum for content comparison
* const hash1 = hashString(fileContent1)
* const hash2 = hashString(fileContent2)
* if (hash1 === hash2) console.log('Likely identical')
* ```
*/
declare const hashString: (str: string) => number;
/**
* Converts a string to PascalCase
* @param str - The input string to convert
* @returns A PascalCase string
* @example
* ```ts
* // Basic usage
* pascalCase('hello world') // 'HelloWorld'
* pascalCase('hello-world') // 'HelloWorld'
* pascalCase('hello_world') // 'HelloWorld'
* pascalCase('HELLO_WORLD') // 'HelloWorld'
*
* // TypeScript template literal types
* const result = pascalCase('hello-world')
* // result type: "HelloWorld" (literal type)
*
* // React component naming
* type ComponentName = 'user-profile' | 'nav-bar' | 'side-menu'
* function getComponentName(name: ComponentName): PascalCase<ComponentName> {
* return pascalCase(name) as PascalCase<ComponentName>
* }
* const Component = getComponentName('user-profile')
* // Component type: "UserProfile"
*
* // Class name generation
* const className = pascalCase('base-controller' as const)
* // className type: "BaseController"
* class MyClass extends window[className] {} // TypeScript knows it's "BaseController"
* ```
*/
declare function pascalCase<T extends string>(str: T): PascalCase<T>;
declare function pascalCase(str: string): string;
/**
* Reverses a string while properly handling Unicode characters including emojis
* @param str - The input string to reverse
* @returns A reversed string
* @example
* ```ts
* // Basic usage
* reverse('hello') // 'olleh'
* reverse('world') // 'dlrow'
*
* // Palindrome checking
* const isPalindrome = (str: string) => {
* const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '')
* return cleaned === reverse(cleaned)
* }
* isPalindrome('A man a plan a canal Panama') // true
*
* // Reverse display for RTL testing
* reverse('→ Next') // 'txeN ←'
*
* // Unicode and emoji support (handles complex graphemes)
* reverse('Hello 👋') // '👋 olleH'
* reverse('👨👩👧👦 Family') // 'ylimaF 👨👩👧👦' (family emoji stays intact)
* reverse('café') // 'éfac' (diacritics preserved)
* ```
*/
declare const reverse: (str: string) => string;
/**
* Converts a string to sentence case (first letter of each sentence capitalized)
* @param str - The input string to convert
* @returns A sentence case string
* @example
* ```ts
* // Basic usage
* sentenceCase('hello world') // 'Hello world'
* sentenceCase('HELLO WORLD') // 'Hello world'
* sentenceCase('hello. world! how are you?') // 'Hello. World! How are you?'
* sentenceCase('this is a test.this is another.') // 'This is a test. This is another.'
*
* // TypeScript template literal types
* const result = sentenceCase('hello-world')
* // result type: "Hello world" (literal type)
*
* // User-generated content normalization
* type UserInput = 'SHOUTING TEXT' | 'normal text' | 'MiXeD cAsE'
* function normalizeComment(input: UserInput): SentenceCase<UserInput> {
* return sentenceCase(input) as SentenceCase<UserInput>
* }
* const comment = normalizeComment('SHOUTING TEXT')
* // comment type: "Shouting text"
*
* // Error message formatting
* const errorMsg = sentenceCase('INVALID_USER_INPUT' as const)
* // errorMsg type: "Invalid user input"
* throw new Error(errorMsg) // Clean, readable error message
* ```
*/
declare function sentenceCase<T extends string>(str: T): SentenceCase<T>;
declare function sentenceCase(str: string): string;
/**
* Validates if a string is a valid email format
* @param str - The input string to validate
* @returns True if the string is a valid email format, false otherwise
* @example
* ```ts
* // Basic validation
* isEmail('user@example.com') // true
* isEmail('invalid.email') // false
* isEmail('test@domain.co.uk') // true
*
* // Using with branded types for type safety
* import { isValidEmail, toEmail, type Email } from 'nano-string-utils/types'
*
* // Type guard approach
* const userInput: string = getFormInput()
* if (isValidEmail(userInput)) {
* // userInput is now typed as Email
* sendWelcomeEmail(userInput) // function expects Email type
* }
*
* // Builder function approach
* const email = toEmail('admin@company.com')
* if (email) {
* // email is typed as Email | null
* updateUserEmail(email) // Safe to use
* }
*
* // Type-safe email storage
* interface User {
* id: string
* email: Email // Branded type ensures only valid emails
* }
*
* const createUser = (email: string): User | null => {
* const validEmail = toEmail(email)
* if (!validEmail) return null
* return { id: generateId(), email: validEmail }
* }
* ```
*/
declare const isEmail: (str: string) => boolean;
/**
* Validates if a string is a valid URL format
* @param str - The input string to validate
* @returns True if the string is a valid URL format, false otherwise
* @example
* ```ts
* // Basic validation
* isUrl('https://example.com') // true
* isUrl('http://localhost:3000') // true
* isUrl('not a url') // false
*
* // Using with branded types for type safety
* import { isValidUrl, toUrl, type URL } from 'nano-string-utils/types'
*
* // Type guard for API configuration
* const apiEndpoint: string = process.env.API_URL || ''
* if (isValidUrl(apiEndpoint)) {
* // apiEndpoint is now typed as URL
* const client = createApiClient(apiEndpoint)
* }
*
* // Builder function with null safety
* const url = toUrl(userProvidedUrl)
* if (url) {
* // url is typed as URL | null
* await fetch(url) // TypeScript ensures valid URL
* } else {
* console.error('Invalid URL provided')
* }
*
* // Type-safe configuration
* interface ApiConfig {
* baseUrl: URL // Branded type ensures valid URL
* timeout: number
* }
*
* const config: ApiConfig = {
* baseUrl: toUrl('https://api.example.com')!,
* timeout: 5000
* }
* ```
*/
declare const isUrl: (str: string) => boolean;
/**
* Validates if a string is a valid hexadecimal color code
* @param str - The input string to validate
* @returns True if the string is a valid hex color format, false otherwise
* @example
* ```ts
* // Basic validation
* isHexColor('#fff') // true (3-digit)
* isHexColor('#ffffff') // true (6-digit)
* isHexColor('#fff8') // true (4-digit with alpha)
* isHexColor('#ffffff80') // true (8-digit with alpha)
* isHexColor('#FFF') // true (case-insensitive)
* isHexColor('fff') // false (missing #)
* isHexColor('#gggggg') // false (invalid characters)
*
* // Real-world usage in design systems
* const validateThemeColor = (color: string) => {
* if (!isHexColor(color)) {
* throw new Error(`Invalid color: ${color}`)
* }
* return color
* }
*
* // Form validation for color picker
* const colorInput = document.getElementById('color')
* colorInput.addEventListener('change', (e) => {
* const value = e.target.value
* if (isHexColor(value)) {
* applyColor(value)
* } else {
* showError('Please enter a valid hex color')
* }
* })
*
* // Using with branded types for type safety
* import { isValidHexColor, toHexColor, type HexColor } from 'nano-string-utils/types'
*
* // Type guard approach
* const userColor: string = getUserInput()
* if (isValidHexColor(userColor)) {
* // userColor is now typed as HexColor
* setThemeColor(userColor) // function expects HexColor type
* }
*
* // Builder function approach
* const color = toHexColor('#ff5733')
* if (color) {
* // color is typed as HexColor | null
* updateBrandColor(color) // Safe to use
* }
*
* // Type-safe design system
* interface Theme {
* primary: HexColor
* secondary: HexColor
* accent: HexColor
* }
*
* const createTheme = (colors: Record<string, string>): Theme | null => {
* const primary = toHexColor(colors.primary)
* const secondary = toHexColor(colors.secondary)
* const accent = toHexColor(colors.accent)
*
* if (!primary || !secondary || !accent) return null
*
* return { primary, secondary, accent }
* }
* ```
*/
declare const isHexColor: (str: string) => boolean;
/**
* Validates if a string represents a numeric value (integer or decimal)
* @param str - The input string to validate
* @returns True if the string is a valid numeric value, false otherwise
* @example
* ```ts
* // Basic validation
* isNumeric('42') // true (integer)
* isNumeric('-17') // true (negative integer)
* isNumeric('3.14') // true (decimal)
* isNumeric('-0.5') // true (negative decimal)
* isNumeric(' 42 ') // true (whitespace trimmed)
* isNumeric('abc') // false (non-numeric)
* isNumeric('') // false (empty string)
* isNumeric('Infinity') // false (special value)
* isNumeric('1e5') // false (scientific notation)
*
* // Real-world usage in form validation
* const validateAge = (input: string) => {
* if (!isNumeric(input)) {
* throw new Error('Please enter a valid number')
* }
* const age = parseInt(input, 10)
* return age >= 0 && age <= 150
* }
*
* // CSV data parsing
* const parseCSVRow = (row: string[]) => {
* return row.map(cell => {
* if (isNumeric(cell)) {
* return parseFloat(cell)
* }
* return cell
* })
* }
*
* // API validation
* app.post('/price', (req, res) => {
* const { price } = req.body
* if (!isNumeric(price)) {
* return res.status(400).json({ error: 'Invalid price format' })
* }
* const priceValue = parseFloat(price)
* if (priceValue < 0) {
* return res.status(400).json({ error: 'Price must be positive' })
* }
* // Process valid price...
* })
*
* // Using with branded types for type safety
* import { isValidNumeric, toNumericString, type NumericString } from 'nano-string-utils/types'
*
* // Type guard approach
* const userInput: string = getFormValue()
* if (isValidNumeric(userInput)) {
* // userInput is now typed as NumericString
* processNumericValue(userInput) // function expects NumericString type
* }
*
* // Builder function approach
* const numericStr = toNumericString('123.45')
* if (numericStr) {
* // numericStr is typed as NumericString | null
* saveToDatabase(numericStr) // Safe to use
* }
*
* // Type-safe data processing
* interface DataRow {
* id: NumericString
* quantity: NumericString
* price: NumericString
* }
*
* const parseDataRow = (raw: Record<string, string>): DataRow | null => {
* const id = toNumericString(raw.id)
* const quantity = toNumericString(raw.quantity)
* const price = toNumericString(raw.price)
*
* if (!id || !quantity || !price) return null
*
* return { id, quantity, price }
* }
* ```
*/
declare const isNumeric: (str: string) => boolean;
/**
* Validates if a string contains only alphanumeric characters (a-z, A-Z, 0-9).
*
* This function performs strict ASCII alphanumeric validation, making it ideal for
* validating usernames, identifiers, and other inputs that should contain only
* letters and numbers without special characters or whitespace.
*
* @param str - The string to validate
* @returns `true` if the string contains only alphanumeric characters, `false` otherwise
*
* @example
* ```typescript
* isAlphanumeric('hello123'); // true
* isAlphanumeric('HelloWorld'); // true
* isAlphanumeric('test_user'); // false (underscore not allowed)
* isAlphanumeric('hello world'); // false (whitespace not allowed)
* isAlphanumeric('hello-world'); // false (hyphen not allowed)
* isAlphanumeric('café'); // false (Unicode not allowed)
* isAlphanumeric(''); // false (empty string)
* isAlphanumeric(' abc '); // false (whitespace not allowed)
* ```
*
* @example
* ```typescript
* // Validate username
* const username = 'user123';
* if (isAlphanumeric(username)) {
* console.log('Valid username');
* }
*
* // Validate identifier
* const id = 'ABC123XYZ';
* if (isAlphanumeric(id)) {
* console.log('Valid identifier');
* }
* ```
*/
declare const isAlphanumeric: (str: string) => boolean;
/**
* Validates if a string is a valid UUID (Universally Unique Identifier).
*
* This function validates UUIDs in the standard 8-4-4-4-12 format with hyphens.
* It accepts all UUID versions (v1-v5) and the NIL UUID, and is case-insensitive.
* Perfect for validating API identifiers, session tokens, and database IDs.
*
* @param str - The string to validate
* @returns `true` if the string is a valid UUID, `false` otherwise
*
* @example
* ```typescript
* // Valid UUIDs
* isUUID('550e8400-e29b-41d4-a716-446655440000'); // true (v4)
* isUUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8'); // true (v1)
* isUUID('00000000-0000-0000-0000-000000000000'); // true (NIL UUID)
* isUUID('550E8400-E29B-41D4-A716-446655440000'); // true (uppercase)
*
* // Invalid UUIDs
* isUUID('550e8400e29b41d4a716446655440000'); // false (no hyphens)
* isUUID('550e8400-e29b-41d4-a716'); // false (too short)
* isUUID('not-a-uuid'); // false (invalid format)
* isUUID('550e8400-e29b-41d4-a716-44665544000g'); // false (invalid char 'g')
* isUUID(''); // false (empty string)
* ```
*
* @example
* ```typescript
* // Validate API identifier
* const sessionId = '550e8400-e29b-41d4-a716-446655440000';
* if (isUUID(sessionId)) {
* console.log('Valid session token');
* }
*
* // Validate database ID
* const userId = request.params.id;
* if (isUUID(userId)) {
* // Fetch user from database
* const user = await db.users.findById(userId);
* }
* ```
*/
declare const isUUID: (str: string) => boolean;
/**
* Counts the number of words in a string
* @param str - The input string to count words from
* @returns The number of words in the string
* @example
* ```ts
* // Basic usage
* wordCount('Hello world') // 2
* wordCount(' Multiple spaces ') // 2
*
* // Reading time estimation
* const article = 'Your blog post content here...'
* const words = wordCount(article)
* const readingTime = Math.ceil(words / 200) // Assuming 200 words per minute
* // Display: `${readingTime} min read`
*
* // Content validation
* const description = 'Product description text'
* if (wordCount(description) < 10) {
* console.log('Description too short')
* }
*
* // Twitter/social media character validation
* const tweet = 'Your tweet here'
* const words = wordCount(tweet)
* console.log(`${words} words`)
* ```
*/
declare const wordCount: (str: string) => number;
/**
* Options for template function
*/
interface TemplateOptions {
/** Custom delimiters for placeholders. Default: ['{{', '}}'] */
delimiters?: [string, string];
/** Value to use for missing variables. Default: '' */
fallback?: string | null;
/** Keep unmatched placeholders in output. Default: false */
keepUnmatched?: boolean;
}
/**
* Interpolates variables in a template string
* @param str - Template string with placeholders
* @param data - Data object with values to interpolate
* @param options - Optional configuration
* @returns Processed string with interpolated values
* @example
* ```ts
* // Basic usage with type inference
* template('Hello {{name}}!', { name: 'World' })
* // 'Hello World!'
*
* // Nested object access with type safety
* interface User {
* name: string
* age: number
* address: { city: string }
* }
* const user: User = {
* name: 'Alice',
* age: 30,
* address: { city: 'NYC' }
* }
* template('{{name}} from {{address.city}}', user)
* // 'Alice from NYC'
*
* // Function overloads - without options
* const result1 = template('Hi {{name}}', { name: 'Bob' })
* // TypeScript knows: (str: string, data: Record<string, any>) => string
*
* // Function overloads - with options interface
* const options: TemplateOptions = {
* delimiters: ['${', '}'],
* fallback: 'N/A',
* keepUnmatched: false
* }
* const result2 = template('Hello ${name}!', { name: 'Eve' }, options)
* // TypeScript knows: (str: string, data: Record<string, any>, options: TemplateOptions) => string
*
* // Type-safe template data
* type Config = { apiUrl: string; timeout: number }
* const config: Config = { apiUrl: 'https://api.example.com', timeout: 5000 }
* const url = template('{{apiUrl}}/users', config)
* // TypeScript ensures config has required properties
* ```
*/
declare function template(str: string, data: Record<string, any>): string;
declare function template(str: string, data: Record<string, any>, options: TemplateOptions): string;
/**
* Interpolates variables in a template string with HTML escaping
* @param str - Template string with placeholders
* @param data - Data object with values to interpolate (strings will be HTML-escaped)
* @param options - Optional configuration
* @returns Processed string with interpolated and escaped values
* @example
* templateSafe('Hello {{name}}!', { name: '<script>alert("XSS")</script>' })
* // 'Hello <script>alert("XSS")</script>!'
*
* templateSafe('User: {{user.name}}', {
* user: { name: '<b>John</b>' }
* })
* // 'User: <b>John</b>'
*/
declare function templateSafe(str: string, data: Record<string, any>): string;
declare function templateSafe(str: string, data: Record<string, any>, options: TemplateOptions): string;
/**
* Pads a string to a given length by adding characters to both sides
* @param str - The string to pad
* @param length - The target length
* @param chars - The characters to use for padding (default: ' ')
* @returns The padded string
* @example
* ```ts
* // Basic usage
* pad('Hi', 6) // ' Hi '
* pad('Hi', 6, '-') // '--Hi--'
* pad('Hi', 7, '-') // '--Hi---'
*
* // Center-aligned table columns
* const header = pad('Status', 20)
* // ' Status '
*
* // Decorative text formatting
* const title = pad(' Welcome ', 40, '=')
* // '=============== Welcome ================'
*
* // Receipt/invoice formatting
* const total = pad('TOTAL', 30, ' ')
* // ' TOTAL '
* ```
*/
declare function pad(str: string, length: number): string;
declare function pad(str: string, length: number, chars: string): string;
/**
* Pads a string to a given length by adding characters to the left
* @param str - The string to pad
* @param length - The target length
* @param chars - The characters to use for padding (default: ' ')
* @returns The padded string
* @example
* ```ts
* // Basic usage
* padStart('5', 3, '0') // '005'
* padStart('Hi', 5, '.') // '...Hi'
*
* // Zero-padding IDs and numbers
* const orderId = padStart(String(42), 8, '0') // '00000042'
* const invoice = padStart(String(123), 6, '0') // '000123'
*
* // Right-aligned table columns
* const price = padStart('$99.99', 15) // ' $99.99'
* const quantity = padStart('42', 10) // ' 42'
*
* // Log timestamps alignment
* const seconds = padStart(String(7), 2, '0') // '07'
* const minutes = padStart(String(5), 2, '0') // '05'
* // Result: '05:07'
* ```
*/
declare function padStart(str: string, length: number): string;
declare function padStart(str: string, length: number, chars: string): string;
/**
* Pads a string to a given length by adding characters to the right
* @param str - The string to pad
* @param length - The target length
* @param chars - The characters to use for padding (default: ' ')
* @returns The padded string
* @example
* ```ts
* // Basic usage
* padEnd('Hi', 5, '.') // 'Hi...'
* padEnd('5', 3, '0') // '500'
*
* // Left-aligned table columns
* const name = padEnd('John', 20) // 'John '
* const status = padEnd('Active', 15) // 'Active '
*
* // Log formatting with consistent column widths
* const level = padEnd('[INFO]', 10) // '[INFO] '
* const module = padEnd('auth', 15) // 'auth '
* console.log(level + module + 'User logged in')
*
* // Invoice line items
* const item = padEnd('Widget A', 30, '.') // 'Widget A......................'
* ```
*/
declare function padEnd(str: string, length: number): string;
declare function padEnd(str: string, length: number, chars: string): string;
/**
* Removes diacritics/accents from Latin characters for search normalization and URL safety
* @param str - The input string to deburr
* @returns String with diacritics removed
* @example
* ```ts
* // Basic usage
* deburr('café') // 'cafe'
* deburr('naïve') // 'naive'
* deburr('Bjørn') // 'Bjorn'
* deburr('São Paulo') // 'Sao Paulo'
*
* // Search normalization - matching with/without accents
* const searchTerm = deburr(userInput.toLowerCase())
* const results = items.filter(item =>
* deburr(item.name.toLowerCase()).includes(searchTerm)
* )
* // Finds "café" when searching for "cafe"
*
* // URL-friendly slugs
* const title = 'Crème Brûlée Recipe'
* const slug = deburr(title).toLowerCase().replace(/\s+/g, '-')
* // 'creme-brulee-recipe'
*
* // File naming from international text
* const filename = deburr('Ångström_Café.pdf')
* // 'Angstrom_Cafe.pdf'
*
* // Database search optimization
* const normalized = deburr(userQuery)
* // Match against pre-normalized database columns
* ```
*/
declare function deburr(str: string): string;
/**
* Options for configuring the titleCase function behavior
*/
interface TitleCaseOptions {
/** Additional words to treat as exceptions (not capitalized unless first/last) */
exceptions?: string[];
}
declare function titleCase<T extends string>(str: T, options?: TitleCaseOptions): TitleCase<T>;
declare function titleCase(str: string, options?: TitleCaseOptions): string;
/**
* Converts a string to CONSTANT_CASE
* @param str - The input string to convert
* @returns A CONSTANT_CASE string
* @example
* ```ts
* // Basic usage
* constantCase('hello world') // 'HELLO_WORLD'
* constantCase('helloWorld') // 'HELLO_WORLD'
* constantCase('hello-world') // 'HELLO_WORLD'
* constantCase('__hello__world__') // 'HELLO_WORLD'
*
* // TypeScript template literal types
* const result = constantCase('helloWorld')
* // result type: "HELLO_WORLD" (literal type)
*
* // Environment variable naming
* type ConfigKey = 'apiUrl' | 'dbHost' | 'maxRetries'
* function getEnvKey(key: ConfigKey): ConstantCase<ConfigKey> {
* return constantCase(key) as ConstantCase<ConfigKey>
* }
* const envVar = getEnvKey('apiUrl')
* // envVar type: "API_URL"
* process.env[envVar] // TypeScript knows this is process.env.API_URL
*
* // Redux action types
* const action = constantCase('fetchUserSuccess' as const)
* // action type: "FETCH_USER_SUCCESS"
* ```
*/
declare function constantCase<T extends string>(str: T): ConstantCase<T>;
declare function constantCase(str: string): string;
/**
* Converts a string to dot.case
* @param str - The input string to convert
* @returns A dot.case string
* @example
* ```ts
* // Basic usage
* dotCase('hello world') // 'hello.world'
* dotCase('helloWorld') // 'hello.world'
* dotCase('hello-world') // 'hello.world'
* dotCase('hello_world') // 'hello.world'
* dotCase('XMLHttpRequest') // 'xml.http.request'
*
* // TypeScript template literal types
* const result = dotCase('helloWorld')
* // result type: "hello.world" (literal type)
*
* // Object path notation
* type NestedKey = 'userName' | 'userEmail' | 'userSettings'
* function getObjectPath(key: NestedKey): DotCase<NestedKey> {
* return dotCase(key) as DotCase<NestedKey>
* }
* const path = getObjectPath('userName')
* // path type: "user.name"
*
* // Config file keys
* const configKey = dotCase('databaseHost' as const)
* // configKey type: "database.host"
* const value = config[configKey] // TypeScript knows this is config["database.host"]
* ```
*/
declare function dotCase<T extends string>(str: T): DotCase<T>;
declare function dotCase(str: string): string;
/**
* Converts a string to path/case
* @param str - The input string to convert
* @returns A path/case string
* @example
* ```ts
* // Basic usage
* pathCase('hello world') // 'hello/world'
* pathCase('helloWorld') // 'hello/world'
* pathCase('hello-world') // 'hello/world'
* pathCase('hello_world') // 'hello/world'
* pathCase('XMLHttpRequest') // 'xml/http/request'
*
* // TypeScript template literal types
* const result = pathCase('helloWorld')
* // result type: "hello/world" (literal type)
*
* // URL path generation
* type Route = 'userProfile' | 'accountSettings' | 'helpCenter'
* function getPath(route: Route): PathCase<Route> {
* return pathCase(route) as PathCase<Route>
* }
* const urlPath = getPath('userProfile')
* // urlPath type: "user/profile"
* const fullUrl = `/app/${urlPath}` // "/app/user/profile"
*
* // File system paths
* const filePath = pathCase('MyDocuments' as const)
* // filePath type: "my/documents"
* ```
*/
declare function pathCase<T extends string>(str: T): PathCase<T>;
declare function pathCase(str: string): string;
/**
* Split a string into an array of grapheme clusters (user-perceived characters)
* Handles emojis, combining characters, and other complex Unicode properly
* @param str - The string to split
* @returns Array of grapheme clusters
* @example
* ```ts
* // Basic usage
* graphemes('hello') // ['h', 'e', 'l', 'l', 'o']
* graphemes('café') // ['c', 'a', 'f', 'é']
*
* // Complex emoji handling (families, skin tones, flags)
* graphemes('👨👩👧👦🎈') // ['👨👩👧👦', '🎈']
* graphemes('👍🏽') // ['👍🏽'] (emoji + skin tone = 1 grapheme)
*
* // Accurate character counting for limits
* const tweet = 'Hello 👋 World 🌍'
* const charCount = graphemes(tweet).length // 13 (not 15)
* const remaining = 280 - charCount
*
* // Text truncation preserving emojis
* const text = 'React ⚛️ is awesome!'
* const chars = graphemes(text)
* const truncated = chars.slice(0, 10).join('') // 'React ⚛️ i'
*
* // Reverse text correctly
* const reversed = graphemes('Hello 👨👩👧').reverse().join('')
* // '👨👩👧 olleH' (family emoji stays intact)
* ```
*/
declare function graphemes(str: string): string[];
/**
* Converts a string into an array of Unicode code points for character analysis
* @param str - The input string
* @returns An array of Unicode code point numbers
* @example
* ```ts
* // Basic usage
* codePoints('hello') // [104, 101, 108, 108, 111]
* codePoints('€') // [8364]
* codePoints('👍') // [128077]
*
* // Character validation and filtering
* const input = 'Hello123'
* const points = codePoints(input)
* const hasNumbers = points.some(p => p >= 48 && p <= 57) // true
* const hasSpecialChars = points.some(p => p > 127) // false
*
* // Emoji detection
* const text = 'Hello 👋'
* const points = codePoints(text)
* const hasEmoji = points.some(p => p > 0x1F000) // true
*
* // Character encoding analysis
* const str = 'café'
* const points = codePoints(str)
* // [99, 97, 102, 233] - can determine encoding needs
*
* // Security: detect potentially dangerous characters
* const suspicious = codePoints(userInput).some(p =>
* p === 0x200B || // Zero-width space
* p === 0xFEFF // Byte order mark
* )
* ```
*/
declare function codePoints(str: string): number[];
/**
* Checks if a string contains only ASCII characters (code points 0-127)
* @param str - The input string to check
* @returns True if the string contains only ASCII characters, false otherwise
* @example
* isASCII('Hello World!') // true
* isASCII('café') // false
* isASCII('👍') // false
* isASCII('abc123!@#') // true
* isASCII('') // true
*/
declare function isASCII(str: string): boolean;
/**
* Options for configuring the normalizeWhitespace function behavior
*/
interface NormalizeWhitespaceOptions {
/**
* Whether to trim leading and trailing whitespace
* @default true
*/
trim?: boolean;
/**
* Whether to collapse multiple consecutive spaces into one
* @default true
*/
collapse?: boolean;
/**
* Whether to preserve newlines (only converts them to regular spaces if false)
* @default false
*/
preserveNewlines?: boolean;
}
/**
* Normalizes various Unicode whitespace characters to regular spaces
* @param str - The string to normalize
* @param options - Options for normalization behavior
* @returns The normalized string
* @example
* normalizeWhitespace('hello world') // 'hello world'
* normalizeWhitespace('hello\u00A0world') // 'hello world' (non-breaking space)
* normalizeWhitespace(' hello ') /