UNPKG

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
/** * 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>') * // '&lt;div&gt;Hello &amp; &quot;World&quot;&lt;/div&gt;' * * // XSS prevention in user input * const userInput = '<script>alert("xss")</script>' * const safe = escapeHtml(userInput) * // '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;' * * // Displaying user-generated content safely * const comment = userComment.replace(/</g, '&lt;') * // 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 &lt;img src=x onerror=alert(1)&gt;</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 &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;!' * * templateSafe('User: {{user.name}}', { * user: { name: '<b>John</b>' } * }) * // 'User: &lt;b&gt;John&lt;/b&gt;' */ 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 ') /