nano-string-utils
Version:
Ultra-lightweight string utilities with zero dependencies
1,489 lines (1,428 loc) • 60.3 kB
TypeScript
/**
* Converts a string to a URL-safe slug
* @param str - The input string to slugify
* @returns A URL-safe slug
* @example
* ```ts
* // Basic usage
* slugify('Hello World!') // 'hello-world'
* slugify(' Multiple Spaces ') // 'multiple-spaces'
* slugify('Special #@! Characters') // 'special-characters'
*
* // Using with branded Slug type for type safety
* import { toSlug, ensureSlug, type Slug } from 'nano-string-utils/types'
*
* // Create type-safe slugs
* const slug: Slug = toSlug('Blog Post Title!')
* // slug is 'blog-post-title' with Slug branded type
*
* // Type-safe routing
* interface Route {
* path: Slug
* component: React.FC
* }
*
* const routes: Route[] = [
* { path: toSlug('About Us'), component: AboutPage },
* { path: toSlug('Contact'), component: ContactPage }
* ]
*
* // Ensure slug with validation
* const userInput = 'already-slugified'
* const slug2 = ensureSlug(userInput)
* // If already a valid slug, returns as-is; otherwise slugifies
*
* // Database schema with branded types
* interface BlogPost {
* id: string
* title: string
* slug: Slug // Ensures only valid slugs in DB
* }
*
* const createPost = (title: string): BlogPost => ({
* id: generateId(),
* title,
* slug: toSlug(title)
* })
* ```
*/
declare const slugify: (str: string) => string;
/**
* Truncates a string to a specified length and adds ellipsis
* @param str - The input string to truncate
* @param length - The maximum length of the output string (including ellipsis)
* @param suffix - The suffix to append (default: '...')
* @returns A truncated string with ellipsis
* @example
* ```ts
* // Basic usage
* truncate('Long text here', 10) // 'Long te...'
* truncate('Short', 10) // 'Short'
*
* // Function overloads for different signatures
* const text = 'This is a very long string that needs truncation'
* truncate(text, 20) // Uses default '...' suffix
* truncate(text, 20, '…') // Custom suffix with single character
* truncate(text, 25, ' [more]') // Custom suffix with text
*
* // Strict null checking in TypeScript
* const maybeString: string | null = getUserInput()
* if (maybeString) {
* // TypeScript knows maybeString is string here
* const truncated = truncate(maybeString, 50)
* }
*
* // Safe handling with nullish coalescing
* const description = truncate(product.description ?? '', 100)
*
* // Type-safe truncation in components
* interface CardProps {
* title: string
* body: string | undefined
* }
* function Card({ title, body }: CardProps) {
* return (
* <div>
* <h3>{truncate(title, 50)}</h3>
* <p>{body ? truncate(body, 200) : 'No description'}</p>
* </div>
* )
* }
*
* // Emoji-safe truncation
* 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>') // '<div>Hello & "World"</div>'
* escapeHtml("It's <script>") // 'It's <script>'
*/
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 <script>alert("XSS")</script>!'
*
* templateSafe('User: {{user.name}}', {
* user: { name: '<b>John</b>' }
* })
* // 'User: <b>John</b>'
*/
declare function templateSafe(str: string, data: Record<string, any>): string;
declare function templateSafe(str: string, data: Record<string, any>, options: TemplateOptions): string;
/**
* Pads a string to a given length by adding characters to both sides
* @param str - The string to pad
* @param length - The target length
* @param chars - The characters to use for padding (default: ' ')
* @returns The padded string
* @example
* 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