{"_id":"@alphinex/utils","_rev":"2-29eaffcd3ef8c67865cac8a63e822c95","name":"@alphinex/utils","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@alphinex/utils","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/utils@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"1005a89c1484a5f27a1148078a25ac225fc70f0d","tarball":"https://registry.npmjs.org/@alphinex/utils/-/utils-1.0.0.tgz","fileCount":5,"integrity":"sha512-fIVDeIeISVmU4CjNa2zlSLnvFP1z+Wz8nBBs0MPc1WJHHmOTK93OY/mEBajdUdq3H9Tghr9VOiDjEfOlf8W2tw==","signatures":[{"sig":"MEUCICzbWhr4U8dnmF7l1kaff44c+sJ849Ur23Kfq4Qc3XQ+AiEA5T22GRfkGFYjH2YeRFTvEbJwfNGQ7ma5CXmXJY8fi38=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21232},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Pure, framework-agnostic utility functions (date, number, string, array, object, class merging).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"clsx":"^2.1.1","@alphinex/core":"1.0.0","tailwind-merge":"^2.6.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","eslint":"^9.17.0","vitest":"^2.1.8","typescript":"^5.7.2","@alphinex/build-config":"0.0.0","@alphinex/eslint-config":"0.0.0","@alphinex/typescript-config":"0.0.0"},"_npmOperationalInternal":{"tmp":"tmp/utils_1.0.0_1785852485831_0.26757647933570916","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@alphinex/utils","version":"1.1.0","private":false,"description":"Pure, framework-agnostic utility functions (date, number, string, array, object, class merging).","license":"UNLICENSED","type":"module","sideEffects":false,"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"dependencies":{"clsx":"^2.1.1","tailwind-merge":"^2.6.0","@alphinex/core":"1.0.0"},"devDependencies":{"typescript":"^5.7.2","vite":"^6.0.5","vitest":"^2.1.8","eslint":"^9.17.0","@alphinex/typescript-config":"0.0.0","@alphinex/build-config":"0.0.0","@alphinex/eslint-config":"0.0.0"},"scripts":{"build":"vite build","dev":"vite build --watch","lint":"eslint .","typecheck":"tsc --noEmit","test":"vitest run","clean":"rimraf dist .turbo"},"_nodeVersion":"22.14.0","_id":"@alphinex/utils@1.1.0","dist":{"integrity":"sha512-ZaKyh+6ydYHSA/PijCM4aKW3OKL0CqrHSGS6PrVWo7yi6P0N9+G5xOSzql8tSZ5SN8AjUusLwtyL4GHoLRzehQ==","shasum":"2bd64ed1be404c3d91bcf602fff7a98303790234","tarball":"https://registry.npmjs.org/@alphinex/utils/-/utils-1.1.0.tgz","fileCount":5,"unpackedSize":30582,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDOVtf3aC3dsfRefz89T7OO4vK4TdhFOL2a2HYkhqekqQIgd1TrgHGFvWwkhq2ZfHRRi3tOGk4UbgejMqmMZexxMDY="}]},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"directories":{},"maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/utils_1.1.0_1786525497796_0.5099846395441536"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T14:08:05.662Z","modified":"2026-08-12T09:04:58.175Z","1.0.0":"2026-08-04T14:08:05.958Z","1.1.0":"2026-08-12T09:04:57.972Z"},"license":"UNLICENSED","description":"Pure, framework-agnostic utility functions (date, number, string, array, object, class merging).","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/utils\n\nPure, framework-agnostic utility functions used across the platform: class name merging, number/date\nformatting, string casing, array/object helpers, and timing helpers. Nothing here depends on React\nor any other package — safe to import from app code, server code, or any other package in the repo.\n\n## cn\n\nMerges class names via [clsx](https://github.com/lukeed/clsx), then resolves conflicting Tailwind\nutilities with [tailwind-merge](https://github.com/dcastil/tailwind-merge) so the last one wins\ninstead of both ending up in the class list. Every `@alphinex/ui` component's `className` prop is\nmerged through this:\n\n```ts\nimport { cn } from \"@alphinex/utils\";\n\ncn(\"px-2 py-1\", isActive && \"bg-accent\", \"px-4\"); // -> \"py-1 bg-accent px-4\" (last px-* wins)\n```\n\n## formatCurrency / formatNumber / formatPercent\n\nThin wrappers around `Intl.NumberFormat`, defaulting `locale` to `\"en-US\"`. All other\n`Intl.NumberFormat` options pass through:\n\n```ts\nimport { formatCurrency, formatNumber, formatPercent } from \"@alphinex/utils\";\n\nformatCurrency(1234.5); // \"$1,234.50\"\nformatCurrency(1234.5, { locale: \"en-GB\", currency: \"GBP\" }); // \"£1,234.50\"\nformatNumber(1234567, { maximumFractionDigits: 0 }); // \"1,234,567\"\nformatPercent(0.256, { maximumFractionDigits: 1 }); // \"25.6%\"\n```\n\n## formatDate / formatRelativeTime\n\n`formatDate` wraps `Intl.DateTimeFormat`, accepting a `Date`, epoch number, or ISO string. It\ndefaults to `{ year: \"numeric\", month: \"short\", day: \"numeric\" }`, and merges any options you pass\n_on top of_ those defaults rather than replacing them:\n\n```ts\nimport { formatDate, formatRelativeTime } from \"@alphinex/utils\";\n\nformatDate(new Date()); // \"Aug 5, 2026\"\nformatDate(new Date(), { weekday: \"long\" }); // \"Wednesday, Aug 5, 2026\" — weekday is added, not swapped in\n```\n\n**Known limitation:** because `year`/`month`/`day` are always merged in underneath your own options,\nyou can't use `formatDate` to produce a time-only or otherwise fully custom format that omits them —\npassing e.g. `{ hour: \"2-digit\", minute: \"2-digit\" }` still gets the date parts prepended. For\ntime-only or fully custom output, call `Intl.DateTimeFormat` directly instead of `formatDate`.\n\n`formatRelativeTime` formats a date against \"now\" using `Intl.RelativeTimeFormat`, auto-selecting\nthe largest sensible unit:\n\n```ts\nconst fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);\nformatRelativeTime(fiveMinutesAgo); // \"5 minutes ago\"\n```\n\n## String helpers\n\n`capitalize`, `toCamelCase`, `toKebabCase`, `toPascalCase`, `truncate`. The case converters share a\nword-splitter that handles `camelCase`, `snake_case`, and `kebab-case` input alike:\n\n```ts\nimport { capitalize, toCamelCase, toKebabCase, toPascalCase, truncate } from \"@alphinex/utils\";\n\ncapitalize(\"hello world\"); // \"Hello world\"\ntoCamelCase(\"user_first_name\"); // \"userFirstName\"\ntoKebabCase(\"UserFirstName\"); // \"user-first-name\"\ntoPascalCase(\"user-first-name\"); // \"UserFirstName\"\ntruncate(\"A very long description\", 12); // \"A very lo…\"\n```\n\n## Array helpers\n\n`chunk`, `groupBy`, `unique`, `uniqueBy`:\n\n```ts\nimport { chunk, groupBy, unique, uniqueBy } from \"@alphinex/utils\";\n\nchunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]\nunique([1, 2, 2, 3]); // [1, 2, 3]\nuniqueBy(users, (u) => u.email); // first user per unique email\ngroupBy(orders, (o) => o.status); // { pending: [...], shipped: [...] }\n```\n\n`chunk` throws a `RangeError` if `size <= 0`.\n\n## Object helpers\n\n`isEmptyObject`, `omit`, `pick` — plain, type-safe object shaping:\n\n```ts\nimport { isEmptyObject, omit, pick } from \"@alphinex/utils\";\n\npick(user, [\"id\", \"email\"]); // { id, email }\nomit(user, [\"password\"]); // everything except password\nisEmptyObject({}); // true\n```\n\n## Timing helpers\n\n`debounce` and `throttle`, both returning a wrapped function with a `.cancel()` method\n(`Cancelable`):\n\n```ts\nimport { debounce, throttle } from \"@alphinex/utils\";\n\nconst search = debounce((query: string) => runSearch(query), 300);\ninput.addEventListener(\"input\", (e) => search((e.target as HTMLInputElement).value));\n// search.cancel() to abort a pending call, e.g. on unmount\n\nconst onScroll = throttle(() => updateScrollPosition(), 100);\nwindow.addEventListener(\"scroll\", onScroll);\n```\n\n`debounce` delays invocation until `waitMs` have elapsed since the _last_ call. `throttle` runs at\nmost once per `waitMs`, with a trailing call scheduled for the end of the window if calls kept\ncoming in during it.\n\nSee [documentation/ARCHITECTURE.md](../../documentation/ARCHITECTURE.md) for the full package contract, dependency rules, and roadmap placement.\n\n## formatBytes\n\n```ts\nimport { formatBytes } from \"@alphinex/utils\";\n\nformatBytes(1536); // \"1.5 KB\"\nformatBytes(1024, { standard: \"binary\" }); // \"1 KiB\"\nformatBytes(1_234_567, { decimals: 2 }); // \"1.23 MB\"\n```\n\nDefaults to the 1000-based `KB`/`MB` units, not 1024-based `KiB`/`MiB`. That distinction is not\npedantry: a \"5 MB max upload\" almost always means 5,000,000 bytes, and checking it against\n5 × 1024² silently rejects files the user was told were small enough.\n","readmeFilename":""}