@alphinex/utils
v1.1.0
Published
Pure, framework-agnostic utility functions (date, number, string, array, object, class merging).
Readme
@alphinex/utils
Pure, framework-agnostic utility functions used across the platform: class name merging, number/date formatting, string casing, array/object helpers, and timing helpers. Nothing here depends on React or any other package — safe to import from app code, server code, or any other package in the repo.
cn
Merges class names via clsx, then resolves conflicting Tailwind
utilities with tailwind-merge so the last one wins
instead of both ending up in the class list. Every @alphinex/ui component's className prop is
merged through this:
import { cn } from "@alphinex/utils";
cn("px-2 py-1", isActive && "bg-accent", "px-4"); // -> "py-1 bg-accent px-4" (last px-* wins)formatCurrency / formatNumber / formatPercent
Thin wrappers around Intl.NumberFormat, defaulting locale to "en-US". All other
Intl.NumberFormat options pass through:
import { formatCurrency, formatNumber, formatPercent } from "@alphinex/utils";
formatCurrency(1234.5); // "$1,234.50"
formatCurrency(1234.5, { locale: "en-GB", currency: "GBP" }); // "£1,234.50"
formatNumber(1234567, { maximumFractionDigits: 0 }); // "1,234,567"
formatPercent(0.256, { maximumFractionDigits: 1 }); // "25.6%"formatDate / formatRelativeTime
formatDate wraps Intl.DateTimeFormat, accepting a Date, epoch number, or ISO string. It
defaults to { year: "numeric", month: "short", day: "numeric" }, and merges any options you pass
on top of those defaults rather than replacing them:
import { formatDate, formatRelativeTime } from "@alphinex/utils";
formatDate(new Date()); // "Aug 5, 2026"
formatDate(new Date(), { weekday: "long" }); // "Wednesday, Aug 5, 2026" — weekday is added, not swapped inKnown limitation: because year/month/day are always merged in underneath your own options,
you can't use formatDate to produce a time-only or otherwise fully custom format that omits them —
passing e.g. { hour: "2-digit", minute: "2-digit" } still gets the date parts prepended. For
time-only or fully custom output, call Intl.DateTimeFormat directly instead of formatDate.
formatRelativeTime formats a date against "now" using Intl.RelativeTimeFormat, auto-selecting
the largest sensible unit:
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
formatRelativeTime(fiveMinutesAgo); // "5 minutes ago"String helpers
capitalize, toCamelCase, toKebabCase, toPascalCase, truncate. The case converters share a
word-splitter that handles camelCase, snake_case, and kebab-case input alike:
import { capitalize, toCamelCase, toKebabCase, toPascalCase, truncate } from "@alphinex/utils";
capitalize("hello world"); // "Hello world"
toCamelCase("user_first_name"); // "userFirstName"
toKebabCase("UserFirstName"); // "user-first-name"
toPascalCase("user-first-name"); // "UserFirstName"
truncate("A very long description", 12); // "A very lo…"Array helpers
chunk, groupBy, unique, uniqueBy:
import { chunk, groupBy, unique, uniqueBy } from "@alphinex/utils";
chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
unique([1, 2, 2, 3]); // [1, 2, 3]
uniqueBy(users, (u) => u.email); // first user per unique email
groupBy(orders, (o) => o.status); // { pending: [...], shipped: [...] }chunk throws a RangeError if size <= 0.
Object helpers
isEmptyObject, omit, pick — plain, type-safe object shaping:
import { isEmptyObject, omit, pick } from "@alphinex/utils";
pick(user, ["id", "email"]); // { id, email }
omit(user, ["password"]); // everything except password
isEmptyObject({}); // trueTiming helpers
debounce and throttle, both returning a wrapped function with a .cancel() method
(Cancelable):
import { debounce, throttle } from "@alphinex/utils";
const search = debounce((query: string) => runSearch(query), 300);
input.addEventListener("input", (e) => search((e.target as HTMLInputElement).value));
// search.cancel() to abort a pending call, e.g. on unmount
const onScroll = throttle(() => updateScrollPosition(), 100);
window.addEventListener("scroll", onScroll);debounce delays invocation until waitMs have elapsed since the last call. throttle runs at
most once per waitMs, with a trailing call scheduled for the end of the window if calls kept
coming in during it.
See documentation/ARCHITECTURE.md for the full package contract, dependency rules, and roadmap placement.
formatBytes
import { formatBytes } from "@alphinex/utils";
formatBytes(1536); // "1.5 KB"
formatBytes(1024, { standard: "binary" }); // "1 KiB"
formatBytes(1_234_567, { decimals: 2 }); // "1.23 MB"Defaults to the 1000-based KB/MB units, not 1024-based KiB/MiB. That distinction is not
pedantry: a "5 MB max upload" almost always means 5,000,000 bytes, and checking it against
5 × 1024² silently rejects files the user was told were small enough.
