@gmana/utils
v1.6.0
Published
Utility functions for React and TypeScript projects.
Downloads
885
Maintainers
Readme
@gmana/utils
A lightweight, dependency-free collection of utility functions for TypeScript and React projects.
Installation
npm install @gmana/utils
# or
pnpm add @gmana/utils
# or
bun add @gmana/utilsAPI Reference
Array
chunk<T>(array: T[], size: number): T[][]
Splits an array into chunks of the specified size.
chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]compact<T>(array: (T | Falsy)[]): T[]
Removes all falsy values (null, undefined, false, 0, "") from an array.
compact([1, null, 2, undefined, 3]) // [1, 2, 3]countBy<T, K>(array: T[], keyFn: (item: T) => K): Record<K, number>
Counts occurrences of each key returned by keyFn.
countBy(['a', 'b', 'a'], x => x) // { a: 2, b: 1 }difference<T>(array1: T[], array2: T[]): T[]
Returns elements in array1 that are not in array2.
difference([1, 2, 3], [2]) // [1, 3]drop<T>(array: T[], n: number): T[]
Removes the first n elements from an array.
drop([1, 2, 3, 4], 2) // [3, 4]dropRight<T>(array: T[], n: number): T[]
Removes the last n elements from an array.
dropRight([1, 2, 3, 4], 2) // [1, 2]flattenArray<T>(array: (T | T[])[]): T[]
Flattens one level of nesting.
flattenArray([1, [2, 3], [4]]) // [1, 2, 3, 4]flattenDeepArray<T>(array): T[]
Deeply flattens all levels of nesting.
flattenDeepArray([1, [2, [3, [4]]]]) // [1, 2, 3, 4]groupBy<T, K>(array: T[], key: (item: T) => K): Record<K, T[]>
Groups array elements by the key returned by key.
groupBy([{ type: 'a' }, { type: 'b' }, { type: 'a' }], x => x.type)
// { a: [{ type: 'a' }, { type: 'a' }], b: [{ type: 'b' }] }groupConsecutive<T, K>(array: T[], keyFn: (item: T) => K): T[][]
Groups consecutive elements that share the same key into sub-arrays.
groupConsecutive([1, 1, 2, 2, 1], x => x)
// [[1, 1], [2, 2], [1]]intersection<T>(array1: T[], array2: T[]): T[]
Returns elements common to both arrays.
intersection([1, 2, 3], [2, 3, 4]) // [2, 3]maxBy<T>(array: T[], selector: (item: T) => number): T | undefined
Returns the element with the highest value per selector.
maxBy([{ n: 1 }, { n: 3 }, { n: 2 }], x => x.n) // { n: 3 }meanBy<T>(array: T[], selector: (item: T) => number): number
Returns the average of values returned by selector.
meanBy([{ n: 1 }, { n: 2 }, { n: 3 }], x => x.n) // 2minBy<T>(array: T[], selector: (item: T) => number): T | undefined
Returns the element with the lowest value per selector.
minBy([{ n: 1 }, { n: 3 }, { n: 2 }], x => x.n) // { n: 1 }partition<T>(array: T[], predicate: (item: T, index: number) => boolean): [T[], T[]]
Splits an array into two groups: elements that satisfy the predicate and those that don't.
partition([1, 2, 3, 4], x => x % 2 === 0) // [[2, 4], [1, 3]]sample<T>(array: T[], n?: number): T[]
Returns n random elements from the array (default 1).
sample([1, 2, 3, 4, 5], 2) // e.g. [3, 5]shuffle<T>(array: T[]): T[]
Returns a new array with elements shuffled using the Fisher-Yates algorithm.
shuffle([1, 2, 3, 4]) // e.g. [3, 1, 4, 2]sortBy<T>(array: T[], ...selectors: ((item: T) => unknown)[]): T[]
Sorts by one or more selector functions.
sortBy(
[{ last: 'B', first: 'Z' }, { last: 'A', first: 'A' }, { last: 'A', first: 'B' }],
u => u.last,
u => u.first
)
// [{ last: 'A', first: 'A' }, { last: 'A', first: 'B' }, { last: 'B', first: 'Z' }]sumBy<T>(array: T[], selector: (item: T) => number): number
Sums the values returned by selector.
sumBy([{ price: 10 }, { price: 20 }, { price: 5 }], x => x.price) // 35symmetricDifference<T>(array1: T[], array2: T[]): T[]
Returns elements present in either array but not both.
symmetricDifference([1, 2, 3], [2, 3, 4]) // [1, 4]take<T>(array: T[], n: number): T[]
Takes the first n elements.
take([1, 2, 3, 4], 2) // [1, 2]takeRight<T>(array: T[], n: number): T[]
Takes the last n elements.
takeRight([1, 2, 3, 4], 2) // [3, 4]unique<T>(array: T[]): T[]
Removes duplicate values.
unique([1, 2, 2, 3, 3]) // [1, 2, 3]uniqueBy<T, K>(array: T[], keyFn: (item: T) => K): T[]
Removes duplicates based on the key returned by keyFn.
uniqueBy([{ id: 1, v: 'a' }, { id: 1, v: 'b' }, { id: 2, v: 'c' }], x => x.id)
// [{ id: 1, v: 'a' }, { id: 2, v: 'c' }]zip<T>(...arrays: T): Array<[...]>
Zips multiple arrays together into an array of tuples.
zip([1, 2, 3], ['a', 'b', 'c']) // [[1, 'a'], [2, 'b'], [3, 'c']]Object
compactObject<T>(input: T, options?: CompactOptions): Partial<T>
Removes empty/falsy values from an object.
interface CompactOptions {
compactArrays?: boolean
removeEmptyArrays?: boolean
isEmpty?: (value: unknown) => boolean
}compactObject({ a: 1, b: null, c: '' }) // { a: 1 }
compactObject({ a: [1, null, 2] }, { compactArrays: true }) // { a: [1, 2] }pick<T, K extends keyof T>(names: K[], obj: T): Pick<T, K>
Picks specified keys from an object. Also available in curried form.
pick(['a', 'b'], { a: 1, b: 2, c: 3 }) // { a: 1, b: 2 }
pick(['a', 'b'])({ a: 1, b: 2, c: 3 }) // { a: 1, b: 2 }flatten(obj: Record<string, unknown>, separator?: string): Record<string, unknown>
Flattens a nested object into a single level using the given separator (default ".").
flatten({ a: { b: { c: 1 } } }) // { 'a.b.c': 1 }
flatten({ a: { b: 1 } }, '_') // { 'a_b': 1 }String
getInitialLetter(fullName?: string | null, fallback?: string): string
Extracts 1–2 letter initials from a name.
getInitialLetter('Sun Sreng') // 'SS'
getInitialLetter('Sun') // 'S'
getInitialLetter(null, 'N/A') // 'N/A'makeTitle(base: string, site: string, params: TemplateParams): string
Builds a page title from a base, site name, and optional template.
makeTitle('Jobs in Tech', 'Acme', {})
// 'Jobs in Tech | Acme'
makeTitle('Jobs in Tech', 'Acme', { template: '%s - Powered by Acme' })
// 'Jobs in Tech - Powered by Acme'
makeTitle('Jobs in Tech', 'Acme', { template: (title, site) => `${title} :: ${site}` })
// 'Jobs in Tech :: Acme'
makeTitle('Acme OG Preview', 'Acme', { disableSuffix: true })
// 'Acme OG Preview'toCase(input: string, type: CaseType): string
Converts a string to the specified case. Supported types: lowercase, uppercase, sentence, title, snake, kebab, dot, constant, pascal, camel.
toCase('hello world', 'pascal') // 'HelloWorld'
toCase('hello world', 'camel') // 'helloWorld'
toCase('helloWorld', 'snake') // 'hello_world'
toCase('helloWorld', 'kebab') // 'hello-world'
toCase('helloWorld', 'dot') // 'hello.world'
toCase('helloWorld', 'constant') // 'HELLO_WORLD'
toCase('helloWorld', 'title') // 'Hello World'
toCase('helloWorld', 'sentence') // 'Hello world'extendCases(custom: Record<string, CaseDefinition>): void
Registers custom case transformers for use with toCase.
extendCases({
'pipe': {
steps: [tokenize, normalizeLower],
format: (ctx) => ctx.words.join('|'),
},
})
toCase('hello world', 'pipe') // 'hello|world'truncateText(text?: string, options?: TruncateTextOptions): string | undefined
Truncates text with an ellipsis.
interface TruncateTextOptions {
maxLength?: number
ellipsis?: string
preserveWords?: boolean
returnUndefinedIfEmpty?: boolean
}truncateText('Hello world', { maxLength: 7 }) // 'Hell...'
truncateText('Hello world', { maxLength: 7, preserveWords: true }) // 'Hello...'
truncateText('Hello world', { maxLength: 7, ellipsis: '…' }) // 'Hell…'
truncateText('', { returnUndefinedIfEmpty: true }) // undefinedNumber
formatBytes(bytes: number, options?: FormatBytesOptions): string
Formats a byte count to a human-readable string.
formatBytes(1536) // '1.50 KB'
formatBytes(1048576) // '1.00 MB'
formatBytes(1500, { precision: 0 }) // '1 KB'
formatBytes(1500, { base: 1000 }) // '1.50 KB'toBytes(input: ByteInput, options?: Partial<ByteConvertOptions>): number
Converts a byte string to a raw number.
toBytes('2.5gb') // 2684354560
toBytes('1kb') // 1024
toBytes('1kb', { base: 1000 }) // 1000createByteConverter(defaultOptions?): (input, overrides?) => number
Creates a pre-configured byte converter function.
const convert = createByteConverter({ base: 1000 })
convert('1kb') // 1000
convert('1mb') // 1000000formatNumber(value?: number | null, decimalPlaces?: number): string
Formats a number with suffix notation (K, M, B, T).
formatNumber(1500) // '1.5K'
formatNumber(2000000) // '2M'
formatNumber(1234567, 2) // '1.23M'
formatNumber(-1500) // '-1.5K'
formatNumber(0) // '0'formatCurrency(options): string
Formats a number as a localized currency string.
formatCurrency({ amount: 1234.5, currencyCode: 'USD' }) // '$1,234.50'
formatCurrency({ amount: 1234.5, currencyCode: 'EUR', locale: 'de-DE' }) // '1.234,50 €'
formatCurrency({ amount: 1000, currencyCode: 'KHR' }) // '៛1,000'numberToWord(n: number): string
Converts a number to English words.
numberToWord(42) // 'Forty Two'
numberToWord(1000) // 'One Thousand'
numberToWord(1234567.99) // 'One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven Point Eight Nine'numberToWordKm(value: number): string
Converts a number to Khmer words.
numberToWordKm(5) // 'ប្រាំ'
numberToWordKm(1000) // 'មួយពាន់'
numberToWordKm(1234567.89) // 'មួយលាន ពីរសែន បីម៉ឺន បួនពាន់ ប្រាំរយ ហុកសិបប្រាំពីរ ក្បៀស ប្រាំបី ប្រាំបួន'toASCII(s: string): string
Converts Khmer numerals to ASCII digits.
toASCII('០១២៣') // '0123'toKhmer(s: string): string
Converts ASCII digits to Khmer numerals.
toKhmer('0123') // '០១២៣'Date / Time
formatTime(seconds: number, options?: FormatTimeOptions): string
Formats a duration in seconds to a human-readable string.
interface FormatTimeOptions {
format?: 'digital' | 'long' | 'short' | 'compact'
alwaysShowHours?: boolean
roundingMode?: 'floor' | 'ceil' | 'round'
padMinutes?: boolean
separator?: string
}formatTime(3661, { format: 'digital' }) // '1:01:01'
formatTime(3661, { format: 'long' }) // '1 hour 1 minute 1 second'
formatTime(3661, { format: 'short' }) // '1h 1m 1s'
formatTime(3661, { format: 'compact' }) // '1h'parseTime(timeString: string, separator?: string): number
Parses a formatted time string back to seconds.
parseTime('1:01:01') // 3661
parseTime('0:30') // 1800toIso(value?: Date | string): string | undefined
Converts a Date or date string to ISO 8601 format.
toIso(new Date('2024-01-15')) // '2024-01-15T00:00:00.000Z'
toIso('2024-01-15') // '2024-01-15T00:00:00.000Z'
toIso() // undefinedURL / Path
absoluteUrl(path?, options?): string
Creates an absolute URL from a path, with optional query params and fragment.
absoluteUrl('/users', { query: { page: '2' }, baseUrl: 'https://example.com' })
// 'https://example.com/users?page=2'
absoluteUrl('/about', { fragment: 'team', baseUrl: 'https://example.com' })
// 'https://example.com/about#team'joinPaths(segments: (string | null | undefined)[]): string
Joins URL path segments, handling leading/trailing slashes.
joinPaths(['api', 'users', '123']) // 'api/users/123'
joinPaths(['/api/', '/users/', null]) // 'api/users'isUrl(url: string | URL): boolean
Validates a URL, requiring http or https scheme.
isUrl('https://example.com') // true
isUrl('ftp://example.com') // false
isUrl('not-a-url') // falseisValidUrl(url: string): boolean
Validates a URL string (any scheme).
isValidUrl('https://example.com') // true
isValidUrl('ftp://example.com') // true
isValidUrl('not-a-url') // falseType Checking
isArray(value: unknown): boolean
isArray([1, 2, 3]) // true
isArray('hello') // falseisBoolean(value: unknown): boolean
isBoolean(true) // true
isBoolean(1) // falseisEmpty(value: unknown): boolean
Returns true for null, undefined, {}, [], and "".
isEmpty(null) // true
isEmpty([]) // true
isEmpty({}) // true
isEmpty(0) // falseisFunction(value: unknown): boolean
isFunction(() => {}) // true
isFunction('fn') // falseisNumber(value: unknown): boolean
Excludes NaN.
isNumber(42) // true
isNumber(NaN) // falseisObject(value: unknown): boolean
Returns true for non-null objects (excludes arrays).
isObject({ a: 1 }) // true
isObject(null) // falseisString(value: unknown): boolean
isString('hello') // true
isString(42) // falseisSymbol(value: unknown): boolean
isSymbol(Symbol('s')) // true
isSymbol('s') // falseisUndef(value: unknown): boolean
isUndef(undefined) // true
isUndef(null) // falseisValidJsonString(str: string): boolean
isValidJsonString('{"a":1}') // true
isValidJsonString('{bad}') // falseisValidComponentName(name: string, options?): ComponentValidationResult
Validates a kebab-case component name.
isValidComponentName('my-button') // { valid: true, errors: [] }
isValidComponentName('MyButton') // { valid: false, errors: ['...'] }isDev
true when NODE_ENV is "development" or "test".
if (isDev) console.log('debug info')isNavigator
true when the navigator global is available (browser environment).
if (isNavigator) console.log(navigator.userAgent)CSS / Styling
clsx(...args: ClassValue[]): string
Conditionally joins class names.
clsx('foo', { bar: true, baz: false }) // 'foo bar'
clsx('a', undefined, null, 'b') // 'a b'
clsx(['x', { y: true }]) // 'x y'cn(...inputs: ClassValue[]): string
Combines clsx with tailwind-merge to merge Tailwind classes without conflicts.
cn('px-2 py-1', 'px-4') // 'py-1 px-4'
cn('text-red-500', { 'text-blue-500': true }) // 'text-blue-500'Token / Auth
getTokenExpClaim(token: string): number | null
Extracts the exp claim (Unix timestamp) from a JWT without verifying the signature.
const exp = getTokenExpClaim('eyJ...') // e.g. 1713000000isTokenExpired(token: string, offsetSeconds?: number): boolean
Returns true if the JWT is expired, with an optional clock skew offset.
isTokenExpired('eyJ...') // false (if still valid)
isTokenExpired('eyJ...', 300) // true if expiring within 5 minutesOS Detection
getOS(userAgent: string): { type: OS, label: string }
Detects the operating system from a user-agent string.
type OS = 'windows' | 'macos' | 'linux' | 'android' | 'ios' | 'unknown'getOS(navigator.userAgent)
// { type: 'macos', label: 'macOS' }
getOS('Mozilla/5.0 (Windows NT 10.0; Win64; x64)...')
// { type: 'windows', label: 'Windows' }vCard
VCardGenerator.generate(contact: VCardContact): string
Generates a vCard 4.0 format string.
import { VCardGenerator } from '@gmana/utils'
const vcard = VCardGenerator.generate({
firstName: 'Sun',
lastName: 'Sreng',
email: '[email protected]',
organization: 'Gmana',
phone: '+1-555-0100',
})
// 'BEGIN:VCARD\r\nVERSION:4.0\r\n...'VCardGenerator.createDownloadBlob(contact: VCardContact): Blob
Creates a downloadable .vcf blob for use with a download link.
const blob = VCardGenerator.createDownloadBlob({
firstName: 'Sun',
lastName: 'Sreng',
email: '[email protected]',
})
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'contact.vcf'
a.click()License
MIT
