UNPKG

nano-string-utils

Version:

Ultra-lightweight string utilities with zero dependencies

1,489 lines (1,428 loc) 60.3 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 * truncate('Hello 👋 World 🌍!', 10) // 'Hello 👋...' (won't break emoji) * ``` */ 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 * capitalize('hello world') // 'Hello world' * capitalize('HELLO') // 'Hello' */ 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 * stripHtml('<p>Hello <b>World</b></p>') // 'Hello World' * stripHtml('<div>Test</div>') // 'Test' */ declare const stripHtml: (str: string) => string; /** * Escapes HTML special characters in a string * @param str - The input string to escape * @returns A string with HTML entities escaped * @example * escapeHtml('<div>Hello & "World"</div>') // '&lt;div&gt;Hello &amp; &quot;World&quot;&lt;/div&gt;' * escapeHtml("It's <script>") // 'It&#39;s &lt;script&gt;' */ 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 * randomString(10) // 'a3B9x2Kp1m' * randomString(5, 'abc123') // '3a1bc' */ declare function randomString(length: number): string; declare function randomString(length: number, charset: string): string; /** * Generates a simple hash from a string (non-cryptographic) * @param str - The input string to hash * @returns A numeric hash value * @example * hashString('hello') // 1335831723 * hashString('world') // 3582672807 */ 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 * @param str - The input string to reverse * @returns A reversed string * @example * reverse('hello') // 'olleh' * reverse('world') // 'dlrow' */ 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; /** * 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 * wordCount('Hello world') // 2 * wordCount(' Multiple spaces ') // 2 * wordCount('One-word') // 1 */ 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 * pad('Hi', 6) // ' Hi ' * pad('Hi', 6, '-') // '--Hi--' * pad('Hi', 7, '-') // '--Hi---' */ 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 * padStart('5', 3, '0') // '005' * padStart('Hi', 5, '.') // '...Hi' * padStart('Hi', 6, '=-') // '=-=-Hi' */ 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 * padEnd('Hi', 5, '.') // 'Hi...' * padEnd('Hi', 6, '=-') // 'Hi=-=-' * padEnd('5', 3, '0') // '500' */ declare function padEnd(str: string, length: number): string; declare function padEnd(str: string, length: number, chars: string): string; /** * Removes diacritics/accents from Latin characters * @param str - The input string to deburr * @returns String with diacritics removed * @example * deburr('café') // 'cafe' * deburr('naïve') // 'naive' * deburr('Bjørn') // 'Bjorn' * deburr('São Paulo') // 'Sao Paulo' */ 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 * Handles emojis, combining characters, and other complex Unicode properly * @param str - The string to split * @returns Array of grapheme clusters * @example * graphemes('👨‍👩‍👧‍👦🎈') // ['👨‍👩‍👧‍👦', '🎈'] * graphemes('café') // ['c', 'a', 'f', 'é'] * graphemes('hello') // ['h', 'e', 'l', 'l', 'o'] */ declare function graphemes(str: string): string[]; /** * Converts a string into an array of Unicode code points * @param str - The input string * @returns An array of Unicode code point numbers * @example * codePoints('hello') // [104, 101, 108, 108, 111] * codePoints('👍') // [128077] * codePoints('€') // [8364] * codePoints('a👍b') // [97, 128077, 98] */ 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 ') // 'hello' * normalizeWhitespace('hello\n\nworld') // 'hello world' * normalizeWhitespace('hello\n\nworld', { preserveNewlines: true }) // 'hello\n\nworld' * normalizeWhitespace(' hello ', { trim: false }) // ' hello ' * normalizeWhitespace('a b', { collapse: false }) // 'a b' */ declare function normalizeWhitespace(str: string, options?: NormalizeWhitespaceOptions): string; /** * Options for configuring the removeNonPrintable function behavior */ interface RemoveNonPrintableOptions { /** * Whether to preserve space character (U+0020) * @default true */ keepSpace?: boolean; /** * Whether to preserve newline character (U+000A) * @default false */ keepNewlines?: boolean; /** * Whether to preserve tab character (U+0009) * @default false */ keepTabs?: boolean; /** * Whether to preserve carriage return character (U+000D) * @default false */ keepCarriageReturns?: boolean; } /** * Removes non-printable control characters and formatting characters from strings * @param str - The string to clean * @param options - Options for preserving certain whitespace characters * @returns The string with control characters removed * @example * removeNonPrintable('hello\x00world') // 'helloworld' * removeNonPrintable('hello\nworld') // 'helloworld' * removeNonPrintable('hello\nworld', { keepNewlines: true }) // 'hello\nworld' * removeNonPrintable('hello\tworld', { keepTabs: true }) // 'hello\tworld' * removeNonPrintable('hello\u200Bworld') // 'helloworld' (zero-width space removed) * removeNonPrintable('hello\u202Dworld') // 'helloworld' (left-to-right override removed) * removeNonPrintable('👨‍👩‍👧‍👦') // '👨‍👩‍👧‍👦' (preserves emoji with ZWJ) */ declare function removeNonPrintable(str: string): string; declare function removeNonPrintable(str: string, options: RemoveNonPrintableOptions): string; /** * Options for the toASCII function */ interface ToASCIIOptions { /** * Character to replace non-convertible characters with * If not provided, non-convertible characters are removed */ placeholder?: string; } /** * Converts a string to ASCII-safe representation by removing diacritics, * converting common Unicode symbols, and optionally replacing non-ASCII characters * @param str - The input string to convert * @param options - Options for conversion * @returns ASCII-safe string * @example * toASCII('café') // 'cafe' * toASCII('Hello "world"') // 'Hello "world"' * toASCII('em—dash') // 'em-dash' * toASCII('α β γ') // 'a b g' * toASCII('привет', { placeholder: '?' }) // '??????' */ declare function toASCII(str: string): string; declare function toASCII(str: string, options: ToASCIIOptions): string; /** * Options for configuring the highlight function behavior */ interface HighlightOptions { /** Whether to perform case-sensitive matching (default: false) */ caseSensitive?: boolean; /** Whether to match whole words only (default: false) */ wholeWord?: boolean; /** Custom wrapper tags [opening, closing] (default: ['<mark>', '</mark>']) */ wrapper?: [string, string]; /** CSS class name to add to wrapper element (default: undefined) */ className?: string; /** Whether to escape HTML in the text (default: false) */ escapeHtml?: boolean; } /** * Highlights search terms in text by wrapping matches with markers * @param text - The text to search in * @param terms - The term(s) to highlight (string or array of strings) * @param options - Configuration options * @returns Text with highlighted terms * @example * highlight("The quick brown fox", "quick") * // => "The <mark>quick</mark> brown fox" * * highlight("Error: Connection failed", ["error", "failed"], { wrapper: ['**', '**'] }) * // => "**Error**: Connection **failed**" */ declare function highlight(text: string, terms: string): string; declare function highlight(text: string, terms: string[]): string; declare function highlight(text: string, terms: string, options: HighlightOptions): string; declare function highlight(text: string, terms: string[], options: HighlightOptions): string; /** * Computes a simple string diff comparison between two strings. * Returns a string with inline markers showing additions and deletions. * Uses a simple prefix/suffix algorithm optimized for readability over perfection. * * @param oldStr - The original string * @param newStr - The new string to compare against * @returns A string with diff markers: [-removed-] and {+added+} * * @example * ```typescript * diff('hello world', 'hello beautiful world') * // Returns: 'hello {+beautiful +}world' * * diff('goodbye world', 'hello world') * // Returns: '[-goodbye-]{+hello+} world' * * diff('test', 'test') * // Returns: 'test' * * diff('v1.0.0', 'v1.1.0') * // Returns: 'v1.[-0-]{+1+}.0' * ``` */ declare function diff(oldStr: string, newStr: string): string; /** * Calculates the Levenshtein distance between two strings. * Uses space-optimized dynamic programming with O(min(m,n)) space complexity. * * @param a - First string to compare * @param b - Second string to compare * @param maxDistance - Optional maximum distance threshold. Returns Infinity if exceeded. * @returns The minimum number of single-character edits (insertions, deletions, substitutions) * required to change one string into the other, or Infinity if it exceeds maxDistance * * @example * ```ts * levenshtein('cat', 'bat') // 1 (substitution) * levenshtein('cat', 'cats') // 1 (insertion) * levenshtein('example', 'exmaple') // 2 (transposition) * levenshtein('hello', 'helicopter', 3) // Infinity (exceeds max distance) * ``` */ declare function levenshtein(a: string, b: string, maxDistance?: number): number; /** * Calculates the normalized Levenshtein similarity score between two strings. * Returns a value between 0 and 1, where 1 indicates identical strings and 0 indicates maximum difference. * * @param a - First string to compare * @param b - Second string to compare * @returns A similarity score between 0 and 1, where higher values indicate greater similarity * * @example * ```ts * levenshteinNormalized('hello', 'hello') // 1 (identical) * levenshteinNormalized('cat', 'bat') // 0.667 (1 edit in 3 chars) * levenshteinNormalized('kitten', 'sitting') // 0.571 (3 edits in 7 chars) * levenshteinNormalized('', 'abc') // 0 (completely different) * levenshteinNormalized('hello', 'world') // 0.2 (4 edits in 5 chars) * ``` */ declare function levenshteinNormalized(a: string, b: string): number; /** * Options for fuzzy matching */ interface FuzzyMatchOptions { /** Whether to perform case-sensitive matching (default: false) */ caseSensitive?: boolean; /** Minimum score threshold to consider a match (default: 0) */ threshold?: number; } /** * Result of a fuzzy match operation */ interface FuzzyMatchResult { /** Whether the query matched the target */ matched: boolean; /** Match score between 0 and 1, where 1 is a perfect match */ score: number; } /** * Performs fuzzy string matching with a similarity score * * Uses a Sublime Text-style algorithm where all query characters must appear * in the target string in the same order. Scoring prioritizes: * - Exact matches (score: 1.0) * - Consecutive character matches * - Matches at word boundaries (after spaces, dots, slashes, underscores, hyphens) * - Matches at the beginning of the string * - CamelCase/PascalCase boundaries * * @param query - The search query string * @param target - The target string to match against * @param options - Optional configuration for matching behavior * @returns A FuzzyMatchResult with matched status and score, or null if no match * * @example * ```ts * // Basic usage with interface return type * const result = fuzzyMatch('gto', 'goToLine') * // result type: FuzzyMatchResult | null * // { matched: true, score: 0.875 } * * // Using options interface * const options: FuzzyMatchOptions = { * caseSensitive: false, * threshold: 0.5 * } * const match = fuzzyMatch('ABC', 'abcdef', options) * // Returns null if score < 0.5, otherwise FuzzyMatchResult * * // File search with type safety * function searchFiles(query: string, files: string[]): Array<string & { score: number }> { * return files * .map(file => { * const result = fuzzyMatch(query, file) * return result ? { file, ...result } : null * }) * .filter((r): r is NonNullable<typeof r> => r !== null && r.matched) * .sort((a, b) => b.score - a.score) * .map(({ file, score }) => Object.assign(file, { score })) * } * * // Command palette with threshold * interface Command { * name: string * action: () => void * } * function findCommand(query: string, commands: Command[]): Command | undefined { * const matches = commands * .map(cmd => ({ cmd, result: fuzzyMatch(query, cmd.name, { threshold: 0.3 }) })) * .filter((m): m is { cmd: Command; result: FuzzyMatchResult } => m.result !== null) * .sort((a, b) => b.result.score - a.result.score) * return matches[0]?.cmd * } * ``` */ declare function fuzzyMatch(query: string, target: string): FuzzyMatchResult | null; declare function fuzzyMatch(query: string, target: string, options: FuzzyMatchOptions): FuzzyMatchResult | null; /** * Convert a singular word to plural form using English pluralization rules. * Optionally takes a count to conditionally pluralize based on the number. * * @param word - The word to pluralize * @param count - Optional count to determine if pluralization is needed * @returns The pluralized word (or original if count is 1) * @example * pluralize('box') // 'boxes' * pluralize('item', 1) // 'item' * pluralize('item', 2) // 'items' * pluralize('person') // 'people' */ declare function pluralize(word: string, count?: number): string; /** * Convert a plural word to singular form using English singularization rules. * * @param word - The word to singularize * @returns The singularized word * @example * singularize('boxes') // 'box' * singularize('items') // 'item' * singularize('people') // 'person' * singularize('sheep') // 'sheep' */ declare function singularize(word: string): string; /** * Options for memoization */ interface MemoizeOptions { /** * Maximum number of cached results to store * @default 100 */ maxSize?: number; /** * Custom key generator for cache keys * @default JSON.stringify for multiple args, toString for single arg */ getKey?: (...args: any[]) => string; } /** * Creates a memoized version of a function with LRU cache eviction. * Useful for expensive operations like levenshtein distance or fuzzy matching. * * @param fn - The function to memoize * @param options - Optional configuration for memoization behavior * @returns A memoized version of the input function * * @example * ```ts * // Basic usage with type preservation * const expensiveFn = (n: number): number => { * console.log('Computing...'); * return n * n; * }; * const memoized = memoize(expensiveFn); * // memoized has same type as expensiveFn: (n: number) => number * memoized(5); // Computing... → 25 * memoized(5); // → 25 (cached, no "Computing...") * * // Generic constraints preserve function signatures * function processData<T extends string>(input: T): string { * return 'processed-' + input * } * const cached = memoize(processData) * // cached preserves generic: <T extends string>(input: T) => string * const result = cached('test') // returns: "processed-test" * * // With string utilities and type safety * import { levenshtein, memoize } from 'nano-string-utils'; * const fastLevenshtein = memoize(levenshtein); * // Type preserved: (str1: string, str2: string) => number * * // Interface usage with options * const options: MemoizeOptions = { * maxSize: 50, * getKey: (...args) => args.join('-') * } * const memoizedFn = memoize(expensiveFn, options) * * // Complex type preservation * interface User { id: number; name: string } * const processUser = (user: User): string => 'User: ' + user.name; * const memoizedUser = memoize(processUser, { * getKey: (user) => user.id.toString() * }); * // memoizedUser type: (user: User) => string * * // Async function memoization * const fetchData = async (id: string): Promise<any> => { * // fetch implementation here * return fetch('/api/data/' + id) * } * const cachedFetch = memoize(fetchData) * // cachedFetch type: (id: string) => Promise<any> * ``` */ declare function memoize<T extends (...args: any[]) => any>(fn: T): T; declare function memoize<T extends (...args: any[]) => any>(fn: T, options: MemoizeOptions): T; /** * Represents extracted entities from text */ interface ExtractedEntities { /** Email addresses found in the text */ emails: string[]; /** URLs found in the text */ urls: string[]; /** Social media mentions (@username) */ mentions: string[]; /** Hashtags (#topic) */ hashtags: string[]; /** Phone numbers in various formats */ phones: string[]; /** Dates in common formats */ dates: string[]; /** Currency amounts ($99.99, €50, etc.) */ prices: string[]; } /** * Extracts various entities from text including emails, URLs, mentions, hashtags, phones, dates, and prices * @param text - The text to extract entities from * @returns An object containing arrays of extracted entities * @example * ```ts * // Extract from mixed content * const text = 'Contact @john at john@example.com or call (555) 123-4567. Check #updates at https://example.com. Price: $99.99'; * const entities = extractEntities(text); * // Returns: * // { * // emails: ['john@example.com'], * // urls: ['https://example.com'], * // mentions: ['@john'], * // hashtags: ['#updates'], * // phones: ['(555) 123-4567'], * // dates: [], * // prices: ['$99.99'] * // } * * // Extract from social media content * const tweet = 'Hey @alice and @bob! Check out #javascript #typescript at https://github.com/example'; * const social = extractEntities(tweet); * // social.mentions: ['@alice', '@bob'] * // social.hashtags: ['#javascript', '#typescript'] * * // Extract contact information * const contact = 'Email: support@company.com, Phone: +1-800-555-0100'; * const info = extractEntities(contact); * // info.emails: ['support@company.com'] * // info.phones: ['+1-800-555-0100'] * * // Extract dates and prices * const invoice = 'Invoice Date: 2024-01-15, Due: 01/30/2024, Amount: $1,234.56'; * const billing = extractEntities(invoice); * // billing.dates: ['2024-01-15', '01/30/2024'] * // billing.prices: ['$1,234.56'] * ``` */ declare const extractEntities: (text: string) => ExtractedEntities; /** * Splits text into sentences while intelligently handling abbreviations and edge cases * @param text - The input text to split into sentences * @returns An array of sentences extracted from the text * @example * smartSplit('Hello world. How are you?') // ['Hello world.', 'How are you?'] * smartSplit('Dr. Smith went to the store. He bought milk.') // ['Dr. Smith went to the store.', 'He bought milk.'] * smartSplit('The price is $3.50. What a deal!') // ['The price is $3.50.', 'What a deal!'] */ declare const smartSplit: (text: string) => string[]; /** * Options for humanizing a list */ interface HumanizeListOptions { /** The conjunction to use between the last two items. Default: "and" */ conjunction?: string; /** Whether to use Oxford comma (comma before conjunction in lists of 3+). Default: true */ oxford?: boolean; /** Whether to wrap items in double quotes. Default: false */ quotes?: boolean; } /** * Converts an array into a human-readable list with proper grammar * @param items - Array of items to convert to a human-readable list * @param optio